FD.io VPP  v18.01-8-g0eacf49
Vector Packet Processing
flow_report_classify.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <vnet/flow/flow_report.h>
17 #include <vnet/api_errno.h>
18 
19 /* Common prefix of tcp and udp headers
20  * containing only source and destination port fields */
21 typedef struct
22 {
23  u16 src_port, dst_port;
25 
27 
28 u8 *
30  flow_report_t * fr,
31  ip4_address_t * collector_address,
32  ip4_address_t * src_address,
33  u16 collector_port)
34 {
38  u32 flow_table_index = fr->opaque.as_uword;
39  u8 *ip_start;
40  ip4_header_t *ip;
41  ip6_header_t *ip6;
42  tcpudp_header_t *tcpudp;
43  udp_header_t *udp;
48  ipfix_field_specifier_t *first_field;
49  u8 *rewrite = 0;
51  u32 field_count = 0;
52  u32 field_index = 0;
53  flow_report_stream_t *stream;
54  u8 ip_version;
55  u8 transport_protocol;
56  u8 *virt_mask;
57  u8 *real_mask;
58 
59  stream = &frm->streams[fr->stream_index];
60 
61  ipfix_classify_table_t *table = &fcm->tables[flow_table_index];
62 
63  ip_version = table->ip_version;
64  transport_protocol = table->transport_protocol;
65 
66  tblp = pool_elt_at_index (vcm->tables, table->classify_table_index);
67 
68  virt_mask = (u8 *) (tblp->mask - tblp->skip_n_vectors);
69  real_mask = (u8 *) (tblp->mask);
70 
71  /* Determine field count */
72  ip_start = virt_mask + sizeof (ethernet_header_t);
73 #define _(field,mask,item,length) \
74  if (((u8 *)&field >= real_mask) && (memcmp(&field, &mask, length) == 0)) \
75  { \
76  field_count++; \
77  \
78  fr->fields_to_send = clib_bitmap_set (fr->fields_to_send, \
79  field_index, 1); \
80  } \
81  field_index++;
83 #undef _
84 
85  /* Add packetTotalCount manually */
86  field_count += 1;
87 
88  /* $$$ enterprise fields, at some later date */
89 
90  /* allocate rewrite space */
91  vec_validate_aligned (rewrite,
93  + field_count * sizeof (ipfix_field_specifier_t) - 1,
95 
96  tp = (ip4_ipfix_template_packet_t *) rewrite;
97  ip = (ip4_header_t *) & tp->ip4;
98  udp = (udp_header_t *) (ip + 1);
99  h = (ipfix_message_header_t *) (udp + 1);
100  s = (ipfix_set_header_t *) (h + 1);
101  t = (ipfix_template_header_t *) (s + 1);
102  first_field = f = (ipfix_field_specifier_t *) (t + 1);
103 
104  ip->ip_version_and_header_length = 0x45;
105  ip->ttl = 254;
106  ip->protocol = IP_PROTOCOL_UDP;
107  ip->src_address.as_u32 = src_address->as_u32;
108  ip->dst_address.as_u32 = collector_address->as_u32;
109  udp->src_port = clib_host_to_net_u16 (stream->src_port);
110  udp->dst_port = clib_host_to_net_u16 (collector_port);
111  udp->length = clib_host_to_net_u16 (vec_len (rewrite) - sizeof (*ip));
112 
113  /* FIXUP: message header export_time */
114  /* FIXUP: message header sequence_number */
115  h->domain_id = clib_host_to_net_u32 (stream->domain_id);
116 
117  /* Take another trip through the mask and build the template */
118  ip_start = virt_mask + sizeof (ethernet_header_t);
119 #define _(field,mask,item,length) \
120  if (((u8 *)&field >= real_mask) && (memcmp(&field, &mask, length) == 0)) \
121  { \
122  f->e_id_length = ipfix_e_id_length (0 /* enterprise */, \
123  item, length); \
124  f++; \
125  }
127 #undef _
128 
129  /* Add packetTotalCount manually */
130  f->e_id_length =
131  ipfix_e_id_length (0 /* enterprise */ , packetTotalCount, 8);
132  f++;
133 
134  /* Back to the template packet... */
135  ip = (ip4_header_t *) & tp->ip4;
136  udp = (udp_header_t *) (ip + 1);
137 
138  ASSERT (f - first_field);
139  /* Field count in this template */
140  t->id_count = ipfix_id_count (fr->template_id, f - first_field);
141 
142  /* set length in octets */
143  s->set_id_length =
144  ipfix_set_id_length (2 /* set_id */ , (u8 *) f - (u8 *) s);
145 
146  /* message length in octets */
147  h->version_length = version_length ((u8 *) f - (u8 *) h);
148 
149  ip->length = clib_host_to_net_u16 ((u8 *) f - (u8 *) ip);
150  ip->checksum = ip4_header_checksum (ip);
151 
152  return rewrite;
153 }
154 
155 vlib_frame_t *
157  flow_report_t * fr,
158  vlib_frame_t * f, u32 * to_next, u32 node_index)
159 {
162  u32 flow_table_index = fr->opaque.as_uword;
165  vnet_classify_entry_t *v, *save_v;
166  vlib_buffer_t *b0 = 0;
167  u32 next_offset = 0;
168  u32 record_offset = 0;
169  u32 bi0 = ~0;
170  int i, j, k;
173  ipfix_set_header_t *s = 0;
174  u8 *ip_start;
175  ip4_header_t *ip;
176  ip6_header_t *ip6;
177  tcpudp_header_t *tcpudp;
178  udp_header_t *udp;
179  int field_index;
180  u32 records_this_buffer;
181  u16 new_l0, old_l0;
182  ip_csum_t sum0;
183  vlib_main_t *vm = frm->vlib_main;
184  flow_report_stream_t *stream;
185  u8 ip_version;
186  u8 transport_protocol;
187  u8 *virt_key;
188 
189  stream = &frm->streams[fr->stream_index];
190 
191  ipfix_classify_table_t *table = &fcm->tables[flow_table_index];
192 
193  ip_version = table->ip_version;
194  transport_protocol = table->transport_protocol;
195 
196  t = pool_elt_at_index (vcm->tables, table->classify_table_index);
197 
198  while (__sync_lock_test_and_set (t->writer_lock, 1))
199  ;
200 
201  for (i = 0; i < t->nbuckets; i++)
202  {
203  b = &t->buckets[i];
204  if (b->offset == 0)
205  continue;
206 
207  save_v = vnet_classify_get_entry (t, b->offset);
208  for (j = 0; j < (1 << b->log2_pages); j++)
209  {
210  for (k = 0; k < t->entries_per_page; k++)
211  {
213  (t, save_v, j * t->entries_per_page + k);
214 
216  continue;
217 
218  /* OK, we have something to send... */
219  if (PREDICT_FALSE (b0 == 0))
220  {
221  if (vlib_buffer_alloc (vm, &bi0, 1) != 1)
222  goto flush;
223  b0 = vlib_get_buffer (vm, bi0);
224 
225  u32 copy_len = sizeof (ip4_header_t) +
226  sizeof (udp_header_t) + sizeof (ipfix_message_header_t);
227  clib_memcpy (b0->data, fr->rewrite, copy_len);
228  b0->current_data = 0;
229  b0->current_length = copy_len;
231  vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0;
232  vnet_buffer (b0)->sw_if_index[VLIB_TX] = frm->fib_index;
233 
234  tp = vlib_buffer_get_current (b0);
235  ip = (ip4_header_t *) & tp->ip4;
236  udp = (udp_header_t *) (ip + 1);
237  h = (ipfix_message_header_t *) (udp + 1);
238  s = (ipfix_set_header_t *) (h + 1);
239 
240  /* FIXUP: message header export_time */
241  h->export_time = (u32)
242  (((f64) frm->unix_time_0) +
243  (vlib_time_now (frm->vlib_main) - frm->vlib_time_0));
244  h->export_time = clib_host_to_net_u32 (h->export_time);
245 
246  /* FIXUP: message header sequence_number */
247  h->sequence_number = stream->sequence_number;
248  h->sequence_number =
249  clib_host_to_net_u32 (h->sequence_number);
250 
251  next_offset = (u32) (((u8 *) (s + 1)) - (u8 *) tp);
252  record_offset = next_offset;
253  records_this_buffer = 0;
254  }
255 
256  field_index = 0;
257  virt_key = (u8 *) (v->key - t->skip_n_vectors);
258  ip_start = virt_key + sizeof (ethernet_header_t);
259 #define _(field,mask,item,length) \
260  if (clib_bitmap_get (fr->fields_to_send, field_index)) \
261  { \
262  clib_memcpy (b0->data + next_offset, &field, \
263  length); \
264  next_offset += length; \
265  } \
266  field_index++;
268 #undef _
269 
270  /* Add packetTotalCount manually */
271  {
272  u64 packets = clib_host_to_net_u64 (v->hits);
273  clib_memcpy (b0->data + next_offset, &packets,
274  sizeof (packets));
275  next_offset += sizeof (packets);
276  }
277  records_this_buffer++;
278  stream->sequence_number++;
279 
280  /* Next record will have the same size as this record */
281  u32 next_record_size = next_offset - record_offset;
282  record_offset = next_offset;
283 
284  if (next_offset + next_record_size > frm->path_mtu)
285  {
287  next_offset -
288  (sizeof (*ip) +
289  sizeof (*udp) +
290  sizeof (*h)));
291  h->version_length =
292  version_length (next_offset -
293  (sizeof (*ip) + sizeof (*udp)));
294  b0->current_length = next_offset;
296 
297  tp = vlib_buffer_get_current (b0);
298  ip = (ip4_header_t *) & tp->ip4;
299  udp = (udp_header_t *) (ip + 1);
300 
301  sum0 = ip->checksum;
302  old_l0 = ip->length;
303  new_l0 = clib_host_to_net_u16 ((u16) next_offset);
304 
305  sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t,
306  length /* changed member */ );
307 
308  ip->checksum = ip_csum_fold (sum0);
309  ip->length = new_l0;
310  udp->length =
311  clib_host_to_net_u16 (b0->current_length - sizeof (*ip));
312 
313  if (frm->udp_checksum)
314  {
315  /* RFC 7011 section 10.3.2. */
316  udp->checksum =
317  ip4_tcp_udp_compute_checksum (vm, b0, ip);
318  if (udp->checksum == 0)
319  udp->checksum = 0xffff;
320  }
321 
322  ASSERT (ip->checksum == ip4_header_checksum (ip));
323 
324  to_next[0] = bi0;
325  f->n_vectors++;
326  to_next++;
327 
328  if (f->n_vectors == VLIB_FRAME_SIZE)
329  {
330  vlib_put_frame_to_node (vm, node_index, f);
331  f = vlib_get_frame_to_node (vm, node_index);
332  f->n_vectors = 0;
333  to_next = vlib_frame_vector_args (f);
334  }
335  b0 = 0;
336  bi0 = ~0;
337  }
338  }
339  }
340  }
341 
342 flush:
343  if (b0)
344  {
346  next_offset -
347  (sizeof (*ip) + sizeof (*udp) +
348  sizeof (*h)));
349  h->version_length = version_length (next_offset -
350  (sizeof (*ip) + sizeof (*udp)));
351  b0->current_length = next_offset;
353 
354  tp = vlib_buffer_get_current (b0);
355  ip = (ip4_header_t *) & tp->ip4;
356  udp = (udp_header_t *) (ip + 1);
357 
358  sum0 = ip->checksum;
359  old_l0 = ip->length;
360  new_l0 = clib_host_to_net_u16 ((u16) next_offset);
361 
362  sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t,
363  length /* changed member */ );
364 
365  ip->checksum = ip_csum_fold (sum0);
366  ip->length = new_l0;
367  udp->length = clib_host_to_net_u16 (b0->current_length - sizeof (*ip));
368 
369  if (frm->udp_checksum)
370  {
371  /* RFC 7011 section 10.3.2. */
372  udp->checksum = ip4_tcp_udp_compute_checksum (vm, b0, ip);
373  if (udp->checksum == 0)
374  udp->checksum = 0xffff;
375  }
376 
377  ASSERT (ip->checksum == ip4_header_checksum (ip));
378 
379  to_next[0] = bi0;
380  f->n_vectors++;
381 
382  b0 = 0;
383  bi0 = ~0;
384  }
385 
386  *(t->writer_lock) = 0;
387  return f;
388 }
389 
390 static clib_error_t *
392  unformat_input_t * input,
393  vlib_cli_command_t * cmd)
394 {
398  ipfix_classify_table_t *table;
399  int rv;
400  int is_add = -1;
401  u32 classify_table_index = ~0;
402  u8 ip_version = 0;
403  u8 transport_protocol = 255;
404  clib_error_t *error = 0;
405 
406  if (fcm->src_port == 0)
407  clib_error_return (0, "call 'set ipfix classify stream' first");
408 
409  memset (&args, 0, sizeof (args));
410 
412  {
413  if (unformat (input, "add"))
414  is_add = 1;
415  else if (unformat (input, "del"))
416  is_add = 0;
417  else if (unformat (input, "%d", &classify_table_index))
418  ;
419  else if (unformat (input, "ip4"))
420  ip_version = 4;
421  else if (unformat (input, "ip6"))
422  ip_version = 6;
423  else if (unformat (input, "tcp"))
424  transport_protocol = 6;
425  else if (unformat (input, "udp"))
426  transport_protocol = 17;
427  else
428  return clib_error_return (0, "unknown input `%U'",
429  format_unformat_error, input);
430  }
431 
432  if (is_add == -1)
433  return clib_error_return (0, "expecting: add|del");
434  if (classify_table_index == ~0)
435  return clib_error_return (0, "classifier table not specified");
436  if (ip_version == 0)
437  return clib_error_return (0, "IP version not specified");
438 
439  table = 0;
440  int i;
441  for (i = 0; i < vec_len (fcm->tables); i++)
443  if (fcm->tables[i].classify_table_index == classify_table_index)
444  {
445  table = &fcm->tables[i];
446  break;
447  }
448 
449  if (is_add)
450  {
451  if (table)
452  return clib_error_return (0,
453  "Specified classifier table already used");
454  table = ipfix_classify_add_table ();
455  table->classify_table_index = classify_table_index;
456  }
457  else
458  {
459  if (!table)
460  return clib_error_return (0,
461  "Specified classifier table not registered");
462  }
463 
464  table->ip_version = ip_version;
465  table->transport_protocol = transport_protocol;
466 
467  args.opaque.as_uword = table - fcm->tables;
470  args.is_add = is_add;
471  args.domain_id = fcm->domain_id;
472  args.src_port = fcm->src_port;
473 
474  rv = vnet_flow_report_add_del (frm, &args, NULL);
475 
477 
478  /* If deleting, or add failed */
479  if (is_add == 0 || (rv && is_add))
480  ipfix_classify_delete_table (table - fcm->tables);
481 
482  return error;
483 }
484 
485 /* *INDENT-OFF* */
486 VLIB_CLI_COMMAND (ipfix_classify_table_add_del_command, static) = {
487  .path = "ipfix classify table",
488  .short_help = "ipfix classify table add|del <table-index>",
490 };
491 /* *INDENT-ON* */
492 
493 static clib_error_t *
495  unformat_input_t * input,
496  vlib_cli_command_t * cmd)
497 {
500  u32 domain_id = 1;
501  u32 src_port = UDP_DST_PORT_ipfix;
502 
504  {
505  if (unformat (input, "domain %d", &domain_id))
506  ;
507  else if (unformat (input, "src-port %d", &src_port))
508  ;
509  else
510  return clib_error_return (0, "unknown input `%U'",
511  format_unformat_error, input);
512  }
513 
514  if (fcm->src_port != 0 &&
515  (fcm->domain_id != domain_id || fcm->src_port != (u16) src_port))
516  {
517  int rv = vnet_stream_change (frm, fcm->domain_id, fcm->src_port,
518  domain_id, (u16) src_port);
519  ASSERT (rv == 0);
520  }
521 
522  fcm->domain_id = domain_id;
523  fcm->src_port = (u16) src_port;
524 
525  return 0;
526 }
527 
528 /* *INDENT-OFF* */
529 VLIB_CLI_COMMAND (set_ipfix_classify_stream_command, static) = {
530  .path = "set ipfix classify stream",
531  .short_help = "set ipfix classify stream"
532  "[domain <domain-id>] [src-port <src-port>]",
534 };
535 /* *INDENT-ON* */
536 
537 static clib_error_t *
539 {
540  clib_error_t *error;
541 
542  if ((error = vlib_call_init_function (vm, flow_report_init)))
543  return error;
544 
545  return 0;
546 }
547 
549 
550 /*
551  * fd.io coding-style-patch-verification: ON
552  *
553  * Local Variables:
554  * eval: (c-set-style "gnu")
555  * End:
556  */
clib_error_t * flow_report_add_del_error_to_clib_error(int error)
Definition: flow_report.c:326
sll srl srl sll sra u16x4 i
Definition: vector_sse2.h:337
ip4_address_t src_address
Definition: ip4_packet.h:164
static_always_inline void ipfix_classify_delete_table(u32 index)
uword as_uword
Definition: flow_report.h:58
static_always_inline u8 ipfix_classify_table_index_valid(u32 index)
static u32 ipfix_e_id_length(int e, u16 id, u16 length)
Definition: ipfix_packet.h:77
#define NULL
Definition: clib.h:55
static f64 vlib_time_now(vlib_main_t *vm)
Definition: main.h:224
u32 stream_index
Definition: flow_report.h:75
int vnet_flow_report_add_del(flow_report_main_t *frm, vnet_flow_report_add_del_args_t *a, u16 *template_id)
Definition: flow_report.c:248
opaque_t opaque
Definition: flow_report.h:83
uword ip_csum_t
Definition: ip_packet.h:90
#define vec_validate_aligned(V, I, A)
Make sure vector is long enough for given index (no header, specified alignment)
Definition: vec.h:443
flow_report_stream_t * streams
Definition: flow_report.h:95
i16 current_data
signed offset in data[], pre_data[] that we are currently processing.
Definition: buffer.h:68
#define VLIB_INIT_FUNCTION(x)
Definition: init.h:111
ip4_address_t dst_address
Definition: ip4_packet.h:164
#define VLIB_BUFFER_TOTAL_LENGTH_VALID
Definition: buffer.h:97
vnet_flow_rewrite_callback_t * rewrite_callback
Definition: flow_report.h:130
vlib_frame_t * vlib_get_frame_to_node(vlib_main_t *vm, u32 to_node_index)
Definition: main.c:182
#define clib_error_return(e, args...)
Definition: error.h:99
unsigned long u64
Definition: types.h:89
#define vlib_call_init_function(vm, x)
Definition: init.h:162
static int vnet_classify_entry_is_free(vnet_classify_entry_t *e)
flow_report_main_t flow_report_main
Definition: flow_report.c:21
#define pool_elt_at_index(p, i)
Returns pointer to element at given index.
Definition: pool.h:459
flow_report_classify_main_t flow_report_classify_main
u16 current_length
Nbytes between current data and the end of this buffer.
Definition: buffer.h:72
#define v
Definition: acl.c:341
u8 * rewrite
Definition: flow_report.h:73
struct _unformat_input_t unformat_input_t
void vlib_put_frame_to_node(vlib_main_t *vm, u32 to_node_index, vlib_frame_t *f)
Definition: main.c:191
static void * vlib_buffer_get_current(vlib_buffer_t *b)
Get pointer to current data to process.
Definition: buffer.h:195
#define PREDICT_FALSE(x)
Definition: clib.h:105
#define VLIB_FRAME_SIZE
Definition: node.h:328
static u32 version_length(u16 length)
Definition: ipfix_packet.h:33
static vnet_classify_entry_t * vnet_classify_entry_at_index(vnet_classify_table_t *t, vnet_classify_entry_t *e, u32 index)
ipfix_classify_table_t * tables
#define UNFORMAT_END_OF_INPUT
Definition: format.h:143
u16 n_vectors
Definition: node.h:344
vlib_main_t * vm
Definition: buffer.c:283
vec_header_t h
Definition: buffer.c:282
static u32 ipfix_id_count(u16 id, u16 count)
Definition: ipfix_packet.h:184
#define clib_memcpy(a, b, c)
Definition: string.h:75
static clib_error_t * ipfix_classify_table_add_del_command_fn(vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd)
static_always_inline ipfix_classify_table_t * ipfix_classify_add_table(void)
#define VLIB_CLI_COMMAND(x,...)
Definition: cli.h:154
struct _vnet_classify_main vnet_classify_main_t
Definition: vnet_classify.h:73
#define ASSERT(truth)
static clib_error_t * set_ipfix_classify_stream_command_fn(vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd)
unsigned int u32
Definition: types.h:88
vlib_main_t * vlib_main
Definition: flow_report.h:117
u16 ip4_tcp_udp_compute_checksum(vlib_main_t *vm, vlib_buffer_t *p0, ip4_header_t *ip0)
Definition: ip4_forward.c:1404
u16 template_id
Definition: flow_report.h:74
vnet_classify_main_t vnet_classify_main
Definition: vnet_classify.c:22
#define foreach_ipfix_field
static u32 ipfix_set_id_length(u16 set_id, u16 length)
Definition: ipfix_packet.h:121
Definition: defs.h:47
unsigned short u16
Definition: types.h:57
#define vec_len(v)
Number of elements in vector (rvalue-only, NULL tolerant)
double f64
Definition: types.h:142
vnet_classify_bucket_t * buckets
unsigned char u8
Definition: types.h:56
int vnet_stream_change(flow_report_main_t *frm, u32 old_domain_id, u16 old_src_port, u32 new_domain_id, u16 new_src_port)
Definition: flow_report.c:379
u8 * ipfix_classify_template_rewrite(flow_report_main_t *frm, flow_report_t *fr, ip4_address_t *collector_address, ip4_address_t *src_address, u16 collector_port)
static void * vlib_frame_vector_args(vlib_frame_t *f)
Get pointer to frame vector data.
Definition: node_funcs.h:267
#define ip_csum_update(sum, old, new, type, field)
Definition: ip_packet.h:139
volatile u32 * writer_lock
vnet_flow_data_callback_t * flow_data_callback
Definition: flow_report.h:129
#define vnet_buffer(b)
Definition: buffer.h:326
u8 * format_unformat_error(u8 *s, va_list *va)
Definition: unformat.c:91
u8 data[0]
Packet data.
Definition: buffer.h:159
static clib_error_t * flow_report_classify_init(vlib_main_t *vm)
u8 ip_version_and_header_length
Definition: ip4_packet.h:132
#define CLIB_CACHE_LINE_BYTES
Definition: cache.h:67
u32 flags
buffer flags: VLIB_BUFFER_FREE_LIST_INDEX_MASK: bits used to store free list index, VLIB_BUFFER_IS_TRACED: trace this buffer.
Definition: buffer.h:75
static u32 vlib_buffer_alloc(vlib_main_t *vm, u32 *buffers, u32 n_buffers)
Allocate buffers into supplied array.
Definition: buffer_funcs.h:341
static vlib_buffer_t * vlib_get_buffer(vlib_main_t *vm, u32 buffer_index)
Translate buffer index into buffer pointer.
Definition: buffer_funcs.h:57
static u16 ip4_header_checksum(ip4_header_t *i)
Definition: ip4_packet.h:239
vlib_frame_t * ipfix_classify_send_flows(flow_report_main_t *frm, flow_report_t *fr, vlib_frame_t *f, u32 *to_next, u32 node_index)
static clib_error_t * flow_report_init(vlib_main_t *vm)
Definition: flow_report.c:509
static u16 ip_csum_fold(ip_csum_t c)
Definition: ip_packet.h:145
uword unformat(unformat_input_t *i, const char *fmt,...)
Definition: unformat.c:972
Definition: defs.h:46
static vnet_classify_entry_t * vnet_classify_get_entry(vnet_classify_table_t *t, uword offset)
static uword unformat_check_input(unformat_input_t *i)
Definition: format.h:169