zmtp_engine.cpp
Go to the documentation of this file.
1 /* SPDX-License-Identifier: MPL-2.0 */
2 
3 #include "precompiled.hpp"
4 #include "macros.hpp"
5 
6 #include <limits.h>
7 #include <string.h>
8 
9 #ifndef ZMQ_HAVE_WINDOWS
10 #include <unistd.h>
11 #endif
12 
13 #include <new>
14 #include <sstream>
15 
16 #include "zmtp_engine.hpp"
17 #include "io_thread.hpp"
18 #include "session_base.hpp"
19 #include "v1_encoder.hpp"
20 #include "v1_decoder.hpp"
21 #include "v2_encoder.hpp"
22 #include "v2_decoder.hpp"
23 #include "v3_1_encoder.hpp"
24 #include "null_mechanism.hpp"
25 #include "plain_client.hpp"
26 #include "plain_server.hpp"
27 #include "gssapi_client.hpp"
28 #include "gssapi_server.hpp"
29 #include "curve_client.hpp"
30 #include "curve_server.hpp"
31 #include "raw_decoder.hpp"
32 #include "raw_encoder.hpp"
33 #include "config.hpp"
34 #include "err.hpp"
35 #include "ip.hpp"
36 #include "likely.hpp"
37 #include "wire.hpp"
38 
39 zmq::zmtp_engine_t::zmtp_engine_t (
40  fd_t fd_,
41  const options_t &options_,
42  const endpoint_uri_pair_t &endpoint_uri_pair_) :
43  stream_engine_base_t (fd_, options_, endpoint_uri_pair_, true),
44  _greeting_size (v2_greeting_size),
45  _greeting_bytes_read (0),
46  _subscription_required (false),
47  _heartbeat_timeout (0)
48 {
49  _next_msg = static_cast<int (stream_engine_base_t::*) (msg_t *)> (
50  &zmtp_engine_t::routing_id_msg);
51  _process_msg = static_cast<int (stream_engine_base_t::*) (msg_t *)> (
52  &zmtp_engine_t::process_routing_id_msg);
53 
54  int rc = _pong_msg.init ();
55  errno_assert (rc == 0);
56 
57  rc = _routing_id_msg.init ();
58  errno_assert (rc == 0);
59 
60  if (_options.heartbeat_interval > 0) {
61  _heartbeat_timeout = _options.heartbeat_timeout;
62  if (_heartbeat_timeout == -1)
63  _heartbeat_timeout = _options.heartbeat_interval;
64  }
65 }
66 
67 zmq::zmtp_engine_t::~zmtp_engine_t ()
68 {
69  const int rc = _routing_id_msg.close ();
70  errno_assert (rc == 0);
71 }
72 
73 void zmq::zmtp_engine_t::plug_internal ()
74 {
75  // start optional timer, to prevent handshake hanging on no input
76  set_handshake_timer ();
77 
78  // Send the 'length' and 'flags' fields of the routing id message.
79  // The 'length' field is encoded in the long format.
80  _outpos = _greeting_send;
81  _outpos[_outsize++] = UCHAR_MAX;
82  put_uint64 (&_outpos[_outsize], _options.routing_id_size + 1);
83  _outsize += 8;
84  _outpos[_outsize++] = 0x7f;
85 
86  set_pollin ();
87  set_pollout ();
88  // Flush all the data that may have been already received downstream.
89  in_event ();
90 }
91 
92 // Position of the revision and minor fields in the greeting.
93 const size_t revision_pos = 10;
94 const size_t minor_pos = 11;
95 
96 bool zmq::zmtp_engine_t::handshake ()
97 {
98  zmq_assert (_greeting_bytes_read < _greeting_size);
99  // Receive the greeting.
100  const int rc = receive_greeting ();
101  if (rc == -1)
102  return false;
103  const bool unversioned = rc != 0;
104 
105  if (!(this
106  ->*select_handshake_fun (unversioned, _greeting_recv[revision_pos],
107  _greeting_recv[minor_pos])) ())
108  return false;
109 
110  // Start polling for output if necessary.
111  if (_outsize == 0)
112  set_pollout ();
113 
114  return true;
115 }
116 
117 int zmq::zmtp_engine_t::receive_greeting ()
118 {
119  bool unversioned = false;
120  while (_greeting_bytes_read < _greeting_size) {
121  const int n = read (_greeting_recv + _greeting_bytes_read,
122  _greeting_size - _greeting_bytes_read);
123  if (n == -1) {
124  if (errno != EAGAIN)
125  error (connection_error);
126  return -1;
127  }
128 
129  _greeting_bytes_read += n;
130 
131  // We have received at least one byte from the peer.
132  // If the first byte is not 0xff, we know that the
133  // peer is using unversioned protocol.
134  if (_greeting_recv[0] != 0xff) {
135  unversioned = true;
136  break;
137  }
138 
139  if (_greeting_bytes_read < signature_size)
140  continue;
141 
142  // Inspect the right-most bit of the 10th byte (which coincides
143  // with the 'flags' field if a regular message was sent).
144  // Zero indicates this is a header of a routing id message
145  // (i.e. the peer is using the unversioned protocol).
146  if (!(_greeting_recv[9] & 0x01)) {
147  unversioned = true;
148  break;
149  }
150 
151  // The peer is using versioned protocol.
152  receive_greeting_versioned ();
153  }
154  return unversioned ? 1 : 0;
155 }
156 
157 void zmq::zmtp_engine_t::receive_greeting_versioned ()
158 {
159  // Send the major version number.
160  if (_outpos + _outsize == _greeting_send + signature_size) {
161  if (_outsize == 0)
162  set_pollout ();
163  _outpos[_outsize++] = 3; // Major version number
164  }
165 
166  if (_greeting_bytes_read > signature_size) {
167  if (_outpos + _outsize == _greeting_send + signature_size + 1) {
168  if (_outsize == 0)
169  set_pollout ();
170 
171  // Use ZMTP/2.0 to talk to older peers.
172  if (_greeting_recv[revision_pos] == ZMTP_1_0
173  || _greeting_recv[revision_pos] == ZMTP_2_0)
174  _outpos[_outsize++] = _options.type;
175  else {
176  _outpos[_outsize++] = 1; // Minor version number
177  memset (_outpos + _outsize, 0, 20);
178 
179  zmq_assert (_options.mechanism == ZMQ_NULL
180  || _options.mechanism == ZMQ_PLAIN
181  || _options.mechanism == ZMQ_CURVE
182  || _options.mechanism == ZMQ_GSSAPI);
183 
184  if (_options.mechanism == ZMQ_NULL)
185  memcpy (_outpos + _outsize, "NULL", 4);
186  else if (_options.mechanism == ZMQ_PLAIN)
187  memcpy (_outpos + _outsize, "PLAIN", 5);
188  else if (_options.mechanism == ZMQ_GSSAPI)
189  memcpy (_outpos + _outsize, "GSSAPI", 6);
190  else if (_options.mechanism == ZMQ_CURVE)
191  memcpy (_outpos + _outsize, "CURVE", 5);
192  _outsize += 20;
193  memset (_outpos + _outsize, 0, 32);
194  _outsize += 32;
195  _greeting_size = v3_greeting_size;
196  }
197  }
198  }
199 }
200 
201 zmq::zmtp_engine_t::handshake_fun_t zmq::zmtp_engine_t::select_handshake_fun (
202  bool unversioned_, unsigned char revision_, unsigned char minor_)
203 {
204  // Is the peer using ZMTP/1.0 with no revision number?
205  if (unversioned_) {
206  return &zmtp_engine_t::handshake_v1_0_unversioned;
207  }
208  switch (revision_) {
209  case ZMTP_1_0:
210  return &zmtp_engine_t::handshake_v1_0;
211  case ZMTP_2_0:
212  return &zmtp_engine_t::handshake_v2_0;
213  case ZMTP_3_x:
214  switch (minor_) {
215  case 0:
216  return &zmtp_engine_t::handshake_v3_0;
217  default:
218  return &zmtp_engine_t::handshake_v3_1;
219  }
220  default:
221  return &zmtp_engine_t::handshake_v3_1;
222  }
223 }
224 
225 bool zmq::zmtp_engine_t::handshake_v1_0_unversioned ()
226 {
227  // We send and receive rest of routing id message
228  if (session ()->zap_enabled ()) {
229  // reject ZMTP 1.0 connections if ZAP is enabled
230  error (protocol_error);
231  return false;
232  }
233 
234  _encoder = new (std::nothrow) v1_encoder_t (_options.out_batch_size);
235  alloc_assert (_encoder);
236 
237  _decoder = new (std::nothrow)
238  v1_decoder_t (_options.in_batch_size, _options.maxmsgsize);
239  alloc_assert (_decoder);
240 
241  // We have already sent the message header.
242  // Since there is no way to tell the encoder to
243  // skip the message header, we simply throw that
244  // header data away.
245  const size_t header_size =
246  _options.routing_id_size + 1 >= UCHAR_MAX ? 10 : 2;
247  unsigned char tmp[10], *bufferp = tmp;
248 
249  // Prepare the routing id message and load it into encoder.
250  // Then consume bytes we have already sent to the peer.
251  int rc = _routing_id_msg.close ();
252  zmq_assert (rc == 0);
253  rc = _routing_id_msg.init_size (_options.routing_id_size);
254  zmq_assert (rc == 0);
255  memcpy (_routing_id_msg.data (), _options.routing_id,
256  _options.routing_id_size);
257  _encoder->load_msg (&_routing_id_msg);
258  const size_t buffer_size = _encoder->encode (&bufferp, header_size);
259  zmq_assert (buffer_size == header_size);
260 
261  // Make sure the decoder sees the data we have already received.
262  _inpos = _greeting_recv;
263  _insize = _greeting_bytes_read;
264 
265  // To allow for interoperability with peers that do not forward
266  // their subscriptions, we inject a phantom subscription message
267  // message into the incoming message stream.
268  if (_options.type == ZMQ_PUB || _options.type == ZMQ_XPUB)
269  _subscription_required = true;
270 
271  // We are sending our routing id now and the next message
272  // will come from the socket.
273  _next_msg = &zmtp_engine_t::pull_msg_from_session;
274 
275  // We are expecting routing id message.
276  _process_msg = static_cast<int (stream_engine_base_t::*) (msg_t *)> (
277  &zmtp_engine_t::process_routing_id_msg);
278 
279  return true;
280 }
281 
282 bool zmq::zmtp_engine_t::handshake_v1_0 ()
283 {
284  if (session ()->zap_enabled ()) {
285  // reject ZMTP 1.0 connections if ZAP is enabled
286  error (protocol_error);
287  return false;
288  }
289 
290  _encoder = new (std::nothrow) v1_encoder_t (_options.out_batch_size);
291  alloc_assert (_encoder);
292 
293  _decoder = new (std::nothrow)
294  v1_decoder_t (_options.in_batch_size, _options.maxmsgsize);
295  alloc_assert (_decoder);
296 
297  return true;
298 }
299 
300 bool zmq::zmtp_engine_t::handshake_v2_0 ()
301 {
302  if (session ()->zap_enabled ()) {
303  // reject ZMTP 2.0 connections if ZAP is enabled
304  error (protocol_error);
305  return false;
306  }
307 
308  _encoder = new (std::nothrow) v2_encoder_t (_options.out_batch_size);
309  alloc_assert (_encoder);
310 
311  _decoder = new (std::nothrow) v2_decoder_t (
312  _options.in_batch_size, _options.maxmsgsize, _options.zero_copy);
313  alloc_assert (_decoder);
314 
315  return true;
316 }
317 
318 bool zmq::zmtp_engine_t::handshake_v3_x (const bool downgrade_sub_)
319 {
320  if (_options.mechanism == ZMQ_NULL
321  && memcmp (_greeting_recv + 12, "NULL\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
322  20)
323  == 0) {
324  _mechanism = new (std::nothrow)
325  null_mechanism_t (session (), _peer_address, _options);
326  alloc_assert (_mechanism);
327  } else if (_options.mechanism == ZMQ_PLAIN
328  && memcmp (_greeting_recv + 12,
329  "PLAIN\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20)
330  == 0) {
331  if (_options.as_server)
332  _mechanism = new (std::nothrow)
333  plain_server_t (session (), _peer_address, _options);
334  else
335  _mechanism =
336  new (std::nothrow) plain_client_t (session (), _options);
337  alloc_assert (_mechanism);
338  }
339 #ifdef ZMQ_HAVE_CURVE
340  else if (_options.mechanism == ZMQ_CURVE
341  && memcmp (_greeting_recv + 12,
342  "CURVE\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20)
343  == 0) {
344  if (_options.as_server)
345  _mechanism = new (std::nothrow) curve_server_t (
346  session (), _peer_address, _options, downgrade_sub_);
347  else
348  _mechanism = new (std::nothrow)
349  curve_client_t (session (), _options, downgrade_sub_);
350  alloc_assert (_mechanism);
351  }
352 #endif
353 #ifdef HAVE_LIBGSSAPI_KRB5
354  else if (_options.mechanism == ZMQ_GSSAPI
355  && memcmp (_greeting_recv + 12,
356  "GSSAPI\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20)
357  == 0) {
358  if (_options.as_server)
359  _mechanism = new (std::nothrow)
360  gssapi_server_t (session (), _peer_address, _options);
361  else
362  _mechanism =
363  new (std::nothrow) gssapi_client_t (session (), _options);
364  alloc_assert (_mechanism);
365  }
366 #endif
367  else {
368  socket ()->event_handshake_failed_protocol (
369  session ()->get_endpoint (),
371  error (protocol_error);
372  return false;
373  }
374 #ifndef ZMQ_HAVE_CURVE
375  LIBZMQ_UNUSED (downgrade_sub_);
376 #endif
377  _next_msg = &zmtp_engine_t::next_handshake_command;
378  _process_msg = &zmtp_engine_t::process_handshake_command;
379 
380  return true;
381 }
382 
383 bool zmq::zmtp_engine_t::handshake_v3_0 ()
384 {
385  _encoder = new (std::nothrow) v2_encoder_t (_options.out_batch_size);
386  alloc_assert (_encoder);
387 
388  _decoder = new (std::nothrow) v2_decoder_t (
389  _options.in_batch_size, _options.maxmsgsize, _options.zero_copy);
390  alloc_assert (_decoder);
391 
392  return zmq::zmtp_engine_t::handshake_v3_x (true);
393 }
394 
395 bool zmq::zmtp_engine_t::handshake_v3_1 ()
396 {
397  _encoder = new (std::nothrow) v3_1_encoder_t (_options.out_batch_size);
398  alloc_assert (_encoder);
399 
400  _decoder = new (std::nothrow) v2_decoder_t (
401  _options.in_batch_size, _options.maxmsgsize, _options.zero_copy);
402  alloc_assert (_decoder);
403 
404  return zmq::zmtp_engine_t::handshake_v3_x (false);
405 }
406 
407 int zmq::zmtp_engine_t::routing_id_msg (msg_t *msg_)
408 {
409  const int rc = msg_->init_size (_options.routing_id_size);
410  errno_assert (rc == 0);
411  if (_options.routing_id_size > 0)
412  memcpy (msg_->data (), _options.routing_id, _options.routing_id_size);
413  _next_msg = &zmtp_engine_t::pull_msg_from_session;
414  return 0;
415 }
416 
417 int zmq::zmtp_engine_t::process_routing_id_msg (msg_t *msg_)
418 {
419  if (_options.recv_routing_id) {
420  msg_->set_flags (msg_t::routing_id);
421  const int rc = session ()->push_msg (msg_);
422  errno_assert (rc == 0);
423  } else {
424  int rc = msg_->close ();
425  errno_assert (rc == 0);
426  rc = msg_->init ();
427  errno_assert (rc == 0);
428  }
429 
430  if (_subscription_required) {
431  msg_t subscription;
432 
433  // Inject the subscription message, so that also
434  // ZMQ 2.x peers receive published messages.
435  int rc = subscription.init_size (1);
436  errno_assert (rc == 0);
437  *static_cast<unsigned char *> (subscription.data ()) = 1;
438  rc = session ()->push_msg (&subscription);
439  errno_assert (rc == 0);
440  }
441 
442  _process_msg = &zmtp_engine_t::push_msg_to_session;
443 
444  return 0;
445 }
446 
447 int zmq::zmtp_engine_t::produce_ping_message (msg_t *msg_)
448 {
449  // 16-bit TTL + \4PING == 7
450  const size_t ping_ttl_len = msg_t::ping_cmd_name_size + 2;
451  zmq_assert (_mechanism != NULL);
452 
453  int rc = msg_->init_size (ping_ttl_len);
454  errno_assert (rc == 0);
455  msg_->set_flags (msg_t::command);
456  // Copy in the command message
457  memcpy (msg_->data (), "\4PING", msg_t::ping_cmd_name_size);
458 
459  uint16_t ttl_val = htons (_options.heartbeat_ttl);
460  memcpy (static_cast<uint8_t *> (msg_->data ()) + msg_t::ping_cmd_name_size,
461  &ttl_val, sizeof (ttl_val));
462 
463  rc = _mechanism->encode (msg_);
464  _next_msg = &zmtp_engine_t::pull_and_encode;
465  if (!_has_timeout_timer && _heartbeat_timeout > 0) {
466  add_timer (_heartbeat_timeout, heartbeat_timeout_timer_id);
467  _has_timeout_timer = true;
468  }
469  return rc;
470 }
471 
472 int zmq::zmtp_engine_t::produce_pong_message (msg_t *msg_)
473 {
474  zmq_assert (_mechanism != NULL);
475 
476  int rc = msg_->move (_pong_msg);
477  errno_assert (rc == 0);
478 
479  rc = _mechanism->encode (msg_);
480  _next_msg = &zmtp_engine_t::pull_and_encode;
481  return rc;
482 }
483 
484 int zmq::zmtp_engine_t::process_heartbeat_message (msg_t *msg_)
485 {
486  if (msg_->is_ping ()) {
487  // 16-bit TTL + \4PING == 7
488  const size_t ping_ttl_len = msg_t::ping_cmd_name_size + 2;
489  const size_t ping_max_ctx_len = 16;
490  uint16_t remote_heartbeat_ttl;
491 
492  // Get the remote heartbeat TTL to setup the timer
493  memcpy (&remote_heartbeat_ttl,
494  static_cast<uint8_t *> (msg_->data ())
495  + msg_t::ping_cmd_name_size,
496  ping_ttl_len - msg_t::ping_cmd_name_size);
497  remote_heartbeat_ttl = ntohs (remote_heartbeat_ttl);
498  // The remote heartbeat is in 10ths of a second
499  // so we multiply it by 100 to get the timer interval in ms.
500  remote_heartbeat_ttl *= 100;
501 
502  if (!_has_ttl_timer && remote_heartbeat_ttl > 0) {
503  add_timer (remote_heartbeat_ttl, heartbeat_ttl_timer_id);
504  _has_ttl_timer = true;
505  }
506 
507  // As per ZMTP 3.1 the PING command might contain an up to 16 bytes
508  // context which needs to be PONGed back, so build the pong message
509  // here and store it. Truncate it if it's too long.
510  // Given the engine goes straight to out_event, sequential PINGs will
511  // not be a problem.
512  const size_t context_len =
513  std::min (msg_->size () - ping_ttl_len, ping_max_ctx_len);
514  const int rc =
515  _pong_msg.init_size (msg_t::ping_cmd_name_size + context_len);
516  errno_assert (rc == 0);
517  _pong_msg.set_flags (msg_t::command);
518  memcpy (_pong_msg.data (), "\4PONG", msg_t::ping_cmd_name_size);
519  if (context_len > 0)
520  memcpy (static_cast<uint8_t *> (_pong_msg.data ())
521  + msg_t::ping_cmd_name_size,
522  static_cast<uint8_t *> (msg_->data ()) + ping_ttl_len,
523  context_len);
524 
525  _next_msg = static_cast<int (stream_engine_base_t::*) (msg_t *)> (
526  &zmtp_engine_t::produce_pong_message);
527  out_event ();
528  }
529 
530  return 0;
531 }
532 
533 int zmq::zmtp_engine_t::process_command_message (msg_t *msg_)
534 {
535  const uint8_t cmd_name_size =
536  *(static_cast<const uint8_t *> (msg_->data ()));
537  const size_t ping_name_size = msg_t::ping_cmd_name_size - 1;
538  const size_t sub_name_size = msg_t::sub_cmd_name_size - 1;
539  const size_t cancel_name_size = msg_t::cancel_cmd_name_size - 1;
540  // Malformed command
541  if (unlikely (msg_->size () < cmd_name_size + sizeof (cmd_name_size)))
542  return -1;
543 
544  const uint8_t *const cmd_name =
545  static_cast<const uint8_t *> (msg_->data ()) + 1;
546  if (cmd_name_size == ping_name_size
547  && memcmp (cmd_name, "PING", cmd_name_size) == 0)
548  msg_->set_flags (zmq::msg_t::ping);
549  if (cmd_name_size == ping_name_size
550  && memcmp (cmd_name, "PONG", cmd_name_size) == 0)
551  msg_->set_flags (zmq::msg_t::pong);
552  if (cmd_name_size == sub_name_size
553  && memcmp (cmd_name, "SUBSCRIBE", cmd_name_size) == 0)
554  msg_->set_flags (zmq::msg_t::subscribe);
555  if (cmd_name_size == cancel_name_size
556  && memcmp (cmd_name, "CANCEL", cmd_name_size) == 0)
557  msg_->set_flags (zmq::msg_t::cancel);
558 
559  if (msg_->is_ping () || msg_->is_pong ())
560  return process_heartbeat_message (msg_);
561 
562  return 0;
563 }
ip.hpp
zmq::msg_t::subscribe
@ subscribe
Definition: msg.hpp:61
ZMQ_CURVE
#define ZMQ_CURVE
Definition: zmq.h:364
zmq::ZMTP_2_0
@ ZMTP_2_0
Definition: zmtp_engine.hpp:25
NULL
NULL
Definition: test_security_zap.cpp:405
ZMQ_XPUB
#define ZMQ_XPUB
Definition: zmq.h:267
config.hpp
ZMQ_PUB
#define ZMQ_PUB
Definition: zmq.h:259
minor_pos
const size_t minor_pos
Definition: zmtp_engine.cpp:94
raw_encoder.hpp
EAGAIN
#define EAGAIN
Definition: errno.hpp:14
zmtp_engine.hpp
gssapi_server.hpp
precompiled.hpp
zmq_assert
#define zmq_assert(x)
Definition: err.hpp:102
null_mechanism.hpp
errno
int errno
zmq::ZMTP_3_x
@ ZMTP_3_x
Definition: zmtp_engine.hpp:26
error
Definition: cJSON.c:88
wire.hpp
ZMQ_PLAIN
#define ZMQ_PLAIN
Definition: zmq.h:363
ZMQ_PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH
#define ZMQ_PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH
Definition: zmq.h:438
v2_encoder.hpp
alloc_assert
#define alloc_assert(x)
Definition: err.hpp:146
errno_assert
#define errno_assert(x)
Definition: err.hpp:113
zmq::msg_t::cancel
@ cancel
Definition: msg.hpp:62
v1_encoder.hpp
v2_decoder.hpp
macros.hpp
v1_decoder.hpp
LIBZMQ_UNUSED
#define LIBZMQ_UNUSED(object)
Definition: macros.hpp:6
ZMQ_GSSAPI
#define ZMQ_GSSAPI
Definition: zmq.h:365
curve_server.hpp
gssapi_client.hpp
raw_decoder.hpp
n
GLdouble n
Definition: glcorearb.h:4153
plain_server.hpp
versiongenerate.buffer_size
int buffer_size
Definition: versiongenerate.py:65
plain_client.hpp
zmq::put_uint64
void put_uint64(unsigned char *buffer_, uint64_t value_)
Definition: wire.hpp:51
io_thread.hpp
err.hpp
ZMQ_NULL
#define ZMQ_NULL
Definition: zmq.h:362
likely.hpp
fd_t
zmq_fd_t fd_t
Definition: libzmq/tests/testutil.hpp:98
true
#define true
Definition: cJSON.c:65
v3_1_encoder.hpp
zmq::msg_t::pong
@ pong
Definition: msg.hpp:60
zmq::msg_t::ping
@ ping
Definition: msg.hpp:59
session_base.hpp
zmq::ZMTP_1_0
@ ZMTP_1_0
Definition: zmtp_engine.hpp:24
false
#define false
Definition: cJSON.c:70
curve_client.hpp
revision_pos
const size_t revision_pos
Definition: zmtp_engine.cpp:93
unlikely
#define unlikely(x)
Definition: likely.hpp:11
options_
DebugStringOptions options_
Definition: src/google/protobuf/descriptor.cc:2410


libaditof
Author(s):
autogenerated on Wed May 21 2025 02:07:02