Goby v2
queue.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Goby Underwater Autonomy Project Libraries
8 // ("The Goby Libraries").
9 //
10 // The Goby Libraries are free software: you can redistribute them and/or modify
11 // them under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // The Goby Libraries are distributed in the hope that they will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
22 
23 #include "goby/acomms/acomms_constants.h"
24 #include "goby/acomms/dccl.h"
25 #include "goby/common/logger.h"
26 #include "goby/util/dynamic_protobuf_manager.h"
27 
28 #include "queue.h"
29 #include "queue_manager.h"
30 
32 
33 using namespace goby::common::logger;
34 using goby::util::as;
35 
36 goby::acomms::Queue::Queue(const google::protobuf::Descriptor* desc, QueueManager* parent,
37  const protobuf::QueuedMessageEntry& cfg)
38  : desc_(desc), parent_(parent), cfg_(cfg), last_send_time_(goby_time())
39 {
40  process_cfg();
41 }
42 
43 // add a new message
44 bool goby::acomms::Queue::push_message(boost::shared_ptr<google::protobuf::Message> dccl_msg)
45 {
46  protobuf::QueuedMessageMeta meta = meta_from_msg(*dccl_msg);
47  return push_message(dccl_msg, meta);
48 }
49 
50 bool goby::acomms::Queue::push_message(boost::shared_ptr<google::protobuf::Message> dccl_msg,
51  protobuf::QueuedMessageMeta meta)
52 {
53  // loopback if set
54  if (parent_->manip_manager_.has(id(), protobuf::LOOPBACK) && !meta.has_encoded_message())
55  {
56  glog.is(DEBUG1) && glog << group(parent_->glog_push_group())
57  << parent_->msg_string(dccl_msg->GetDescriptor())
58  << ": LOOPBACK manipulator set, sending back to decoder"
59  << std::endl;
60  parent_->signal_receive(*dccl_msg);
61  }
62 
63  parent_->signal_out_route(&meta, *dccl_msg, parent_->cfg_.modem_id());
64 
65  glog.is(DEBUG1) &&
66  glog << group(parent_->glog_push_group()) << parent_->msg_string(dccl_msg->GetDescriptor())
67  << ": attempting to push message (destination: " << meta.dest() << ")" << std::endl;
68 
69  // no queue manipulator set
70  if (parent_->manip_manager_.has(id(), protobuf::NO_QUEUE))
71  {
72  glog.is(DEBUG1) && glog << group(parent_->glog_push_group())
73  << parent_->msg_string(dccl_msg->GetDescriptor())
74  << ": not queuing: NO_QUEUE manipulator is set" << std::endl;
75  return true;
76  }
77  // message is to us, auto-loopback
78  else if (meta.dest() == parent_->modem_id_)
79  {
80  glog.is(DEBUG1) && glog << group(parent_->glog_push_group())
81  << "Message is for us: using loopback, not physical interface"
82  << std::endl;
83 
84  parent_->signal_receive(*dccl_msg);
85 
86  // provide an ACK if desired
87  if ((meta.has_ack_requested() && meta.ack_requested()) || queue_message_options().ack())
88  {
89  protobuf::ModemTransmission ack_msg;
90  ack_msg.set_time(goby::common::goby_time<uint64>());
91  ack_msg.set_src(meta.dest());
92  ack_msg.set_dest(meta.dest());
93  ack_msg.set_type(protobuf::ModemTransmission::ACK);
94 
95  parent_->signal_ack(ack_msg, *dccl_msg);
96  }
97  return true;
98  }
99 
100  if (!meta.has_time())
101  meta.set_time(goby::common::goby_time<uint64>());
102 
103  if (meta.non_repeated_size() == 0)
104  {
105  goby::glog.is(DEBUG1) && glog << group(parent_->glog_out_group()) << warn
106  << "empty message attempted to be pushed to queue " << name()
107  << std::endl;
108  return false;
109  }
110 
111  if (!meta.has_ack_requested())
112  meta.set_ack_requested(queue_message_options().ack());
113  messages_.push_back(QueuedMessage());
114  messages_.back().meta = meta;
115  messages_.back().dccl_msg = dccl_msg;
116 
117  glog.is(DEBUG1) && glog << group(parent_->glog_push_group())
118  << "pushed to send stack (queue size " << size() << "/"
119  << queue_message_options().max_queue() << ")" << std::endl;
120 
121  glog.is(DEBUG2) && glog << group(parent_->glog_push_group()) << "Message: " << *dccl_msg
122  << std::endl;
123  glog.is(DEBUG2) && glog << group(parent_->glog_push_group()) << "Meta: " << meta << std::endl;
124 
125  // pop messages off the stack if the queue is full
126  if (queue_message_options().max_queue() &&
127  messages_.size() > queue_message_options().max_queue())
128  {
129  messages_it it_to_erase =
130  queue_message_options().newest_first() ? messages_.begin() : messages_.end();
131 
132  // want "back" iterator not "end"
133  if (it_to_erase == messages_.end())
134  --it_to_erase;
135 
136  // if we were waiting for an ack for this, erase that too
137  waiting_for_ack_it it = find_ack_value(it_to_erase);
138  if (it != waiting_for_ack_.end())
139  waiting_for_ack_.erase(it);
140 
141  glog.is(DEBUG1) && glog << group(parent_->glog_pop_group()) << "queue exceeded for "
142  << name() << ". removing: " << it_to_erase->meta << std::endl;
143 
144  messages_.erase(it_to_erase);
145  }
146 
147  return true;
148 }
149 
151 goby::acomms::Queue::meta_from_msg(const google::protobuf::Message& dccl_msg)
152 {
153  protobuf::QueuedMessageMeta meta = static_meta_;
154  meta.set_non_repeated_size(parent_->codec_->size(dccl_msg));
155 
156  if (!roles_[protobuf::QueuedMessageEntry::DESTINATION_ID].empty())
157  {
158  boost::any field_value =
159  find_queue_field(roles_[protobuf::QueuedMessageEntry::DESTINATION_ID], dccl_msg);
160 
161  int dest = BROADCAST_ID;
162  if (field_value.type() == typeid(int32))
163  dest = boost::any_cast<int32>(field_value);
164  else if (field_value.type() == typeid(int64))
165  dest = boost::any_cast<int64>(field_value);
166  else if (field_value.type() == typeid(uint32))
167  dest = boost::any_cast<uint32>(field_value);
168  else if (field_value.type() == typeid(uint64))
169  dest = boost::any_cast<uint64>(field_value);
170  else if (!field_value.empty())
171  throw(QueueException("Invalid type " + std::string(field_value.type().name()) +
172  " given for (queue_field).is_dest. Expected integer type"));
173 
174  goby::glog.is(DEBUG2) && goby::glog << group(parent_->glog_push_group_)
175  << "setting dest to " << dest << std::endl;
176 
177  meta.set_dest(dest);
178  }
179 
180  if (!roles_[protobuf::QueuedMessageEntry::SOURCE_ID].empty())
181  {
182  boost::any field_value =
183  find_queue_field(roles_[protobuf::QueuedMessageEntry::SOURCE_ID], dccl_msg);
184 
185  int src = BROADCAST_ID;
186  if (field_value.type() == typeid(int32))
187  src = boost::any_cast<int32>(field_value);
188  else if (field_value.type() == typeid(int64))
189  src = boost::any_cast<int64>(field_value);
190  else if (field_value.type() == typeid(uint32))
191  src = boost::any_cast<uint32>(field_value);
192  else if (field_value.type() == typeid(uint64))
193  src = boost::any_cast<uint64>(field_value);
194  else if (!field_value.empty())
195  throw(QueueException("Invalid type " + std::string(field_value.type().name()) +
196  " given for (queue_field).is_src. Expected integer type"));
197 
198  goby::glog.is(DEBUG2) && goby::glog << group(parent_->glog_push_group_)
199  << "setting source to " << src << std::endl;
200 
201  meta.set_src(src);
202  }
203 
204  if (!roles_[protobuf::QueuedMessageEntry::TIMESTAMP].empty())
205  {
206  boost::any field_value =
207  find_queue_field(roles_[protobuf::QueuedMessageEntry::TIMESTAMP], dccl_msg);
208 
209  if (field_value.type() == typeid(uint64))
210  meta.set_time(boost::any_cast<uint64>(field_value));
211  else if (field_value.type() == typeid(double))
212  meta.set_time(static_cast<uint64>(boost::any_cast<double>(field_value)) * 1e6);
213  else if (field_value.type() == typeid(std::string))
214  meta.set_time(goby::util::as<uint64>(goby::util::as<boost::posix_time::ptime>(
215  boost::any_cast<std::string>(field_value))));
216  else if (!field_value.empty())
217  throw(
218  QueueException("Invalid type " + std::string(field_value.type().name()) +
219  " given for (goby.field).queue.is_time. Expected uint64 contained "
220  "microseconds since UNIX, double containing seconds since UNIX or "
221  "std::string containing as<std::string>(boost::posix_time::ptime)"));
222 
223  goby::glog.is(DEBUG2) && goby::glog
224  << group(parent_->glog_push_group_) << "setting time to "
225  << as<boost::posix_time::ptime>(meta.time()) << std::endl;
226  }
227 
228  glog.is(DEBUG2) && glog << group(parent_->glog_push_group()) << "Meta: " << meta << std::endl;
229  return meta;
230 }
231 
232 boost::any goby::acomms::Queue::find_queue_field(const std::string& field_name,
233  const google::protobuf::Message& msg)
234 {
235  const google::protobuf::Message* current_msg = &msg;
236  const google::protobuf::Descriptor* current_desc = current_msg->GetDescriptor();
237 
238  // split name on "." as subfield delimiter
239  std::vector<std::string> field_names;
240  boost::split(field_names, field_name, boost::is_any_of("."));
241 
242  for (int i = 0, n = field_names.size(); i < n; ++i)
243  {
244  const google::protobuf::FieldDescriptor* field_desc =
245  current_desc->FindFieldByName(field_names[i]);
246  if (!field_desc)
247  throw(QueueException("No such field called " + field_name + " in msg " +
248  current_desc->full_name()));
249 
250  if (field_desc->is_repeated())
251  throw(QueueException("Cannot assign a Queue role to a repeated field"));
252 
253  boost::shared_ptr<FromProtoCppTypeBase> helper =
254  goby::acomms::DCCLTypeHelper::find(field_desc);
255 
256  // last field_name
257  if (i == (n - 1))
258  {
259  return helper->get_value(field_desc, *current_msg);
260  }
261  else if (field_desc->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
262  {
263  throw(QueueException("Cannot access child fields of a non-message field: " +
264  field_names[i]));
265  }
266  else
267  {
268  boost::any value = helper->get_value(field_desc, *current_msg);
269  if (value.empty()) // no submessage in this message
270  return boost::any();
271  else
272  {
273  current_msg = boost::any_cast<const google::protobuf::Message*>(value);
274  current_desc = current_msg->GetDescriptor();
275  }
276  }
277  }
278 
279  return boost::any();
280 }
281 
282 goby::acomms::messages_it goby::acomms::Queue::next_message_it()
283 {
284  messages_it it_to_give =
285  queue_message_options().newest_first() ? messages_.end() : messages_.begin();
286  if (it_to_give == messages_.end())
287  --it_to_give; // want "back" iterator not "end"
288 
289  // find a value that isn't already waiting to be acknowledged
290  while (find_ack_value(it_to_give) != waiting_for_ack_.end())
291  queue_message_options().newest_first() ? --it_to_give : ++it_to_give;
292 
293  return it_to_give;
294 }
295 
296 goby::acomms::QueuedMessage goby::acomms::Queue::give_data(unsigned frame)
297 {
298  messages_it it_to_give = next_message_it();
299 
300  bool ack = it_to_give->meta.ack_requested();
301  // broadcast cannot acknowledge
302  if (it_to_give->meta.dest() == BROADCAST_ID && ack == true)
303  {
304  glog.is(DEBUG1) && glog << group(parent_->glog_pop_group()) << parent_->msg_string(desc_)
305  << ": setting ack=false because BROADCAST (0) cannot ACK messages"
306  << std::endl;
307  ack = false;
308  }
309 
310  it_to_give->meta.set_ack_requested(ack);
311 
312  if (ack)
313  waiting_for_ack_.insert(std::pair<unsigned, messages_it>(frame, it_to_give));
314 
315  last_send_time_ = goby_time();
316  it_to_give->meta.set_last_sent_time(util::as<goby::uint64>(last_send_time_));
317 
318  return *it_to_give;
319 }
320 
321 // gives priority values. returns false if in blackout interval or if no data or if messages of wrong size, true if not in blackout
322 bool goby::acomms::Queue::get_priority_values(double* priority,
323  boost::posix_time::ptime* last_send_time,
324  const protobuf::ModemTransmission& request_msg,
325  const std::string& data)
326 {
327  *priority = common::time_duration2double((goby_time() - last_send_time_)) /
328  queue_message_options().ttl() * queue_message_options().value_base();
329 
330  *last_send_time = last_send_time_;
331 
332  // no messages left to send
333  if (messages_.size() <= waiting_for_ack_.size())
334  return false;
335 
336  protobuf::QueuedMessageMeta& next_msg = next_message_it()->meta;
337 
338  // for followup user-frames, destination must be either zero (broadcast)
339  // or the same as the first user-frame
340 
341  if (last_send_time_ + boost::posix_time::seconds(queue_message_options().blackout_time()) >
342  goby_time())
343  {
344  glog.is(DEBUG1) && glog << group(parent_->glog_priority_group()) << "\t" << name()
345  << " is in blackout" << std::endl;
346  return false;
347  }
348  // wrong size
349  else if (request_msg.has_max_frame_bytes() &&
350  (next_msg.non_repeated_size() > (request_msg.max_frame_bytes() - data.size())))
351  {
352  glog.is(DEBUG1) && glog << group(parent_->glog_priority_group()) << "\t" << name()
353  << " next message is too large {" << next_msg.non_repeated_size()
354  << "}" << std::endl;
355  return false;
356  }
357  // wrong destination
358  else if ((request_msg.has_dest() &&
359  !(request_msg.dest() == QUERY_DESTINATION_ID // can set to a real destination
360  || next_msg.dest() == BROADCAST_ID // can switch to a real destination
361  || request_msg.dest() == next_msg.dest()))) // same as real destination
362  {
363  glog.is(DEBUG1) && glog << group(parent_->glog_priority_group()) << "\t" << name()
364  << " next message has wrong destination (must be BROADCAST (0) or "
365  "same as first user-frame, is "
366  << next_msg.dest() << ")" << std::endl;
367  return false;
368  }
369  // wrong ack value UNLESS message can be broadcast
370  else if ((request_msg.has_ack_requested() && !request_msg.ack_requested() &&
371  next_msg.ack_requested() && request_msg.dest() != acomms::BROADCAST_ID))
372  {
373  glog.is(DEBUG1) && glog << group(parent_->glog_priority_group()) << "\t" << name()
374  << " next message requires ACK and the packet does not"
375  << std::endl;
376  return false;
377  }
378  else // ok!
379  {
380  glog.is(DEBUG1) && glog << group(parent_->glog_priority_group()) << "\t" << name() << " ("
381  << next_msg.non_repeated_size() << "B) has priority value"
382  << ": " << *priority << std::endl;
383  return true;
384  }
385 }
386 
387 bool goby::acomms::Queue::pop_message(unsigned frame)
388 {
389  std::list<QueuedMessage>::iterator back_it = messages_.end();
390  --back_it; // gives us "back" iterator
391  std::list<QueuedMessage>::iterator front_it = messages_.begin();
392 
393  // find the first message that isn't waiting for an ack
394  std::list<QueuedMessage>::iterator it =
395  queue_message_options().newest_first() ? back_it : front_it;
396 
397  while (true)
398  {
399  if (!it->meta.ack_requested())
400  {
401  stream_for_pop(*it);
402  messages_.erase(it);
403  return true;
404  }
405 
406  if (it == (queue_message_options().newest_first() ? front_it : back_it))
407  return false;
408 
409  queue_message_options().newest_first() ? --it : ++it;
410  }
411  return false;
412 }
413 
414 bool goby::acomms::Queue::pop_message_ack(unsigned frame,
415  boost::shared_ptr<google::protobuf::Message>& removed_msg)
416 {
417  // pop message from the ack stack
418  if (waiting_for_ack_.count(frame))
419  {
420  // remove a messages in this frame that needs ack
421  waiting_for_ack_it it = waiting_for_ack_.find(frame);
422  removed_msg = (it->second)->dccl_msg;
423 
424  stream_for_pop(*it->second);
425 
426  // remove the message
427  messages_.erase(it->second);
428  // clear the acknowledgement map entry for this message
429  waiting_for_ack_.erase(it);
430  }
431  else
432  {
433  return false;
434  }
435 
436  return true;
437 }
438 
439 void goby::acomms::Queue::stream_for_pop(const QueuedMessage& queued_msg)
440 {
441  glog.is(DEBUG1) && glog << group(parent_->glog_pop_group()) << parent_->msg_string(desc_)
442  << ": popping from send stack"
443  << " (queue size " << size() - 1 << "/"
444  << queue_message_options().max_queue() << ")" << std::endl;
445 
446  glog.is(DEBUG2) && glog << group(parent_->glog_push_group())
447  << "Message: " << *queued_msg.dccl_msg << std::endl;
448  glog.is(DEBUG2) && glog << group(parent_->glog_push_group()) << "Meta: " << queued_msg.meta
449  << std::endl;
450 }
451 
452 std::vector<boost::shared_ptr<google::protobuf::Message> > goby::acomms::Queue::expire()
453 {
454  std::vector<boost::shared_ptr<google::protobuf::Message> > expired_msgs;
455 
456  while (!messages_.empty())
457  {
458  if ((goby::util::as<boost::posix_time::ptime>(messages_.front().meta.time()) +
459  boost::posix_time::seconds(queue_message_options().ttl())) < goby_time())
460  {
461  expired_msgs.push_back(messages_.front().dccl_msg);
462  glog.is(DEBUG1) && glog << group(parent_->glog_pop_group()) << "expiring"
463  << " from send stack " << name() << " "
464  << messages_.front().meta.time() << " (qsize " << size() - 1
465  << "/" << queue_message_options().max_queue()
466  << "): " << *messages_.front().dccl_msg << std::endl;
467  // if we were waiting for an ack for this, erase that too
468  waiting_for_ack_it it = find_ack_value(messages_.begin());
469  if (it != waiting_for_ack_.end())
470  waiting_for_ack_.erase(it);
471 
472  messages_.pop_front();
473  }
474  else
475  {
476  return expired_msgs;
477  }
478  }
479 
480  return expired_msgs;
481 }
482 
483 goby::acomms::waiting_for_ack_it goby::acomms::Queue::find_ack_value(messages_it it_to_find)
484 {
485  waiting_for_ack_it n = waiting_for_ack_.end();
486  for (waiting_for_ack_it it = waiting_for_ack_.begin(); it != n; ++it)
487  {
488  if (it->second == it_to_find)
489  return it;
490  }
491  return n;
492 }
493 
494 void goby::acomms::Queue::info(std::ostream* os) const
495 {
496  *os << "== Begin Queue [[" << name() << "]] ==\n";
497  *os << "Contains " << messages_.size() << " message(s)."
498  << "\n"
499  << "Configured options: \n"
500  << cfg_.ShortDebugString();
501  *os << "\n== End Queue [[" << name() << "]] ==\n";
502 }
503 
504 void goby::acomms::Queue::flush()
505 {
506  glog.is(DEBUG1) && glog << group(parent_->glog_pop_group()) << "flushing stack " << name()
507  << " (qsize 0)" << std::endl;
508  messages_.clear();
509  waiting_for_ack_.clear();
510 }
511 
512 bool goby::acomms::Queue::clear_ack_queue(unsigned start_frame)
513 {
514  for (waiting_for_ack_it it = waiting_for_ack_.begin(), end = waiting_for_ack_.end(); it != end;)
515  {
516  // clear out acks for frames whose ack wait time has expired (or whose frame
517  // number has come around again. This should avoid losing unack'd data.
518  if (it->first >= start_frame)
519  {
520  glog.is(DEBUG1) &&
521  glog << group(parent_->glog_pop_group()) << name()
522  << ": Clearing ack for queue because last_frame >= current_frame" << std::endl;
523  waiting_for_ack_.erase(it++);
524  }
525  else if (it->second->meta.last_sent_time() +
526  parent_->cfg_.minimum_ack_wait_seconds() * 1e6 <
527  goby_time<uint64>())
528  {
529  glog.is(DEBUG1) && glog << group(parent_->glog_pop_group()) << name()
530  << ": Clearing ack for queue because "
531  << parent_->cfg_.minimum_ack_wait_seconds()
532  << " seconds has elapsed since last send. Last send:"
533  << it->second->meta.last_sent_time() << std::endl;
534  waiting_for_ack_.erase(it++);
535  }
536  else
537  {
538  ++it;
539  }
540  }
541  return waiting_for_ack_.empty();
542 }
543 
544 std::ostream& goby::acomms::operator<<(std::ostream& os, const goby::acomms::Queue& oq)
545 {
546  oq.info(&os);
547  return os;
548 }
549 
550 void goby::acomms::Queue::process_cfg()
551 {
552  roles_.clear();
553  static_meta_.Clear();
554 
555  // used to check that the FIELD_VALUE roles fields actually exist
556  boost::shared_ptr<google::protobuf::Message> new_msg =
557  goby::util::DynamicProtobufManager::new_protobuf_message(desc_);
558 
559  for (int i = 0, n = cfg_.role_size(); i < n; ++i)
560  {
561  std::string role_field;
562 
563  switch (cfg_.role(i).setting())
564  {
565  case protobuf::QueuedMessageEntry::Role::STATIC:
566  {
567  if (!cfg_.role(i).has_static_value())
568  throw(QueueException(
569  "Role " + protobuf::QueuedMessageEntry::RoleType_Name(cfg_.role(i).type()) +
570  " is set to STATIC but has no `static_value`"));
571 
572  switch (cfg_.role(i).type())
573  {
574  case protobuf::QueuedMessageEntry::DESTINATION_ID:
575  static_meta_.set_dest(cfg_.role(i).static_value());
576  break;
577 
578  case protobuf::QueuedMessageEntry::SOURCE_ID:
579  static_meta_.set_src(cfg_.role(i).static_value());
580  break;
581 
582  case protobuf::QueuedMessageEntry::TIMESTAMP:
583  throw(QueueException("TIMESTAMP role cannot be static"));
584  break;
585  }
586  }
587  break;
588 
589  case protobuf::QueuedMessageEntry::Role::FIELD_VALUE:
590  {
591  role_field = cfg_.role(i).field();
592 
593  // check that the FIELD_VALUE roles fields actually exist
594  find_queue_field(role_field, *new_msg);
595  }
596  break;
597  }
598  typedef std::map<protobuf::QueuedMessageEntry::RoleType, std::string> Map;
599 
600  std::pair<Map::iterator, bool> result =
601  roles_.insert(std::make_pair(cfg_.role(i).type(), role_field));
602  if (!result.second)
603  throw(QueueException("Role " +
604  protobuf::QueuedMessageEntry::RoleType_Name(cfg_.role(i).type()) +
605  " was assigned more than once. Each role must have at most one "
606  "field or static value per message."));
607  }
608 }
uint64 goby_time< uint64 >()
Returns current UTC time as integer microseconds since 1970-01-01 00:00:00.
Definition: time.h:113
google::protobuf::uint32 uint32
an unsigned 32 bit integer
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
Definition: time.h:104
const int BROADCAST_ID
special modem id for the broadcast destination - no one is assigned this address. Analogous to 192...
double time_duration2double(boost::posix_time::time_duration time_of_day)
time duration to double number of seconds: good to the microsecond
Definition: time.h:176
google::protobuf::int64 int64
a signed 64 bit integer
std::ostream & operator<<(std::ostream &out, const QueueManager &d)
outputs information about all available messages (same as info_all)
common::FlexOstream glog
Access the Goby logger through this object.
google::protobuf::uint64 uint64
an unsigned 64 bit integer
google::protobuf::int32 int32
a signed 32 bit integer
const int QUERY_DESTINATION_ID
special modem id used internally to goby-acomms for indicating that the MAC layer (amac) is agnostic ...