Goby v2
iridium_shore_driver.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 <boost/bimap.hpp>
24 
25 #include "goby/acomms/modemdriver/driver_exception.h"
26 #include "goby/acomms/modemdriver/rudics_packet.h"
27 #include "goby/common/logger.h"
28 #include "goby/common/time.h"
29 #include "goby/util/binary.h"
30 
31 #include "iridium_shore_driver.h"
32 #include "iridium_shore_sbd.h"
33 
34 using namespace goby::common::logger;
35 using goby::glog;
43 
44 goby::acomms::IridiumShoreDriver::IridiumShoreDriver() : next_frame_(0) { init_iridium_dccl(); }
45 
46 goby::acomms::IridiumShoreDriver::~IridiumShoreDriver() {}
47 
49 {
50  driver_cfg_ = cfg;
51 
52  glog.is(DEBUG1) && glog << group(glog_out_group())
53  << "Goby Shore Iridium RUDICS/SBD driver starting up." << std::endl;
54 
55  rudics_mac_msg_.set_src(driver_cfg_.modem_id());
56  rudics_mac_msg_.set_type(goby::acomms::protobuf::ModemTransmission::DATA);
57  rudics_mac_msg_.set_rate(RATE_RUDICS);
58 
59  rudics_server_.reset(new RUDICSServer(
60  rudics_io_, driver_cfg_.GetExtension(IridiumShoreDriverConfig::rudics_server_port)));
61  mo_sbd_server_.reset(new SBDServer(
62  sbd_io_, driver_cfg_.GetExtension(IridiumShoreDriverConfig::mo_sbd_server_port)));
63 
64  rudics_server_->connect_signal.connect(
65  boost::bind(&IridiumShoreDriver::rudics_connect, this, _1));
66 
67  for (int i = 0, n = driver_cfg_.ExtensionSize(IridiumShoreDriverConfig::modem_id_to_imei);
68  i < n; ++i)
69  modem_id_to_imei_[driver_cfg_.GetExtension(IridiumShoreDriverConfig::modem_id_to_imei, i)
70  .modem_id()] =
71  driver_cfg_.GetExtension(IridiumShoreDriverConfig::modem_id_to_imei, i).imei();
72 }
73 
75 
77  const protobuf::ModemTransmission& orig_msg)
78 {
79  process_transmission(orig_msg);
80 }
81 
82 void goby::acomms::IridiumShoreDriver::process_transmission(protobuf::ModemTransmission msg)
83 {
85 
86  if (!msg.has_frame_start())
87  msg.set_frame_start(next_frame_);
88 
89  // set the frame size, if not set or if it exceeds the max configured
90  if (!msg.has_max_frame_bytes() ||
91  msg.max_frame_bytes() > driver_cfg_.GetExtension(IridiumDriverConfig::max_frame_size))
92  msg.set_max_frame_bytes(driver_cfg_.GetExtension(IridiumDriverConfig::max_frame_size));
93 
94  signal_data_request(&msg);
95 
96  next_frame_ += msg.frame_size();
97 
98  if (!(msg.frame_size() == 0 || msg.frame(0).empty()))
99  {
100  send(msg);
101  }
102 }
103 
105 {
106  // if(glog.is(DEBUG1))
107  // display_state_cfg(&glog);
108  double now = goby_time<double>();
109 
110  for (std::map<ModemId, RemoteNode>::iterator it = remote_.begin(), end = remote_.end();
111  it != end; ++it)
112  {
113  RemoteNode& remote = it->second;
114  boost::shared_ptr<OnCallBase> on_call_base = remote.on_call;
115  ModemId id = it->first;
116 
117  // if we're on either type of call, see if we need to send the "bye" message or hangup
118  if (on_call_base)
119  {
120  // if we're on a call, keep pushing data at the target rate
121  const double send_wait =
122  on_call_base->last_bytes_sent() /
123  (driver_cfg_.GetExtension(IridiumDriverConfig::target_bit_rate) /
124  static_cast<double>(BITS_IN_BYTE));
125 
126  if (now > (on_call_base->last_tx_time() + send_wait))
127  {
128  if (!on_call_base->bye_sent())
129  {
130  rudics_mac_msg_.set_dest(it->first);
131  process_transmission(rudics_mac_msg_);
132  }
133  }
134 
135  if (!on_call_base->bye_sent() &&
136  now > (on_call_base->last_tx_time() +
137  driver_cfg_.GetExtension(IridiumDriverConfig::handshake_hangup_seconds)))
138  {
139  glog.is(DEBUG1) && glog << "Sending bye" << std::endl;
140  rudics_send("bye\r", id);
141  on_call_base->set_bye_sent(true);
142  }
143 
144  if ((on_call_base->bye_received() && on_call_base->bye_sent()) ||
145  (now > (on_call_base->last_rx_tx_time() +
146  driver_cfg_.GetExtension(IridiumDriverConfig::hangup_seconds_after_empty))))
147  {
148  glog.is(DEBUG1) && glog << "Hanging up by disconnecting" << std::endl;
149  typedef boost::bimap<
150  ModemId, boost::shared_ptr<RUDICSConnection> >::left_map::iterator LeftIt;
151  LeftIt client_it = clients_.left.find(id);
152  if (client_it != clients_.left.end())
153  rudics_server_->disconnect(client_it->second);
154  else
155  glog.is(WARN) && glog << "Failed to find connection from ModemId " << id
156  << std::endl;
157  remote_[id].on_call.reset();
158  }
159  }
160  }
161 
162  rudics_io_.poll();
163  receive_sbd_mo();
164 }
165 
166 void goby::acomms::IridiumShoreDriver::receive(const protobuf::ModemTransmission& msg)
167 {
168  glog.is(DEBUG2) && glog << group(glog_in_group()) << msg.DebugString() << std::endl;
169 
170  if (msg.type() == protobuf::ModemTransmission::DATA && msg.ack_requested() &&
171  msg.dest() == driver_cfg_.modem_id())
172  {
173  // make any acks
175  ack.set_type(goby::acomms::protobuf::ModemTransmission::ACK);
176  ack.set_src(msg.dest());
177  ack.set_dest(msg.src());
178  ack.set_rate(msg.rate());
179  for (int i = msg.frame_start(), n = msg.frame_size() + msg.frame_start(); i < n; ++i)
180  ack.add_acked_frame(i);
181  send(ack);
182  }
183 
184  signal_receive(msg);
185 }
186 
187 void goby::acomms::IridiumShoreDriver::send(const protobuf::ModemTransmission& msg)
188 {
189  glog.is(DEBUG2) && glog << group(glog_out_group()) << msg.DebugString() << std::endl;
190 
191  RemoteNode& remote = remote_[msg.dest()];
192 
193  if (msg.rate() == RATE_RUDICS || remote.on_call)
194  {
195  std::string bytes;
196  serialize_iridium_modem_message(&bytes, msg);
197 
198  // frame message
199  std::string rudics_packet;
200  serialize_rudics_packet(bytes, &rudics_packet);
201  rudics_send(rudics_packet, msg.dest());
202  boost::shared_ptr<OnCallBase> on_call_base = remote.on_call;
203  on_call_base->set_last_tx_time(goby_time<double>());
204  on_call_base->set_last_bytes_sent(rudics_packet.size());
205  }
206  else if (msg.rate() == RATE_SBD)
207  {
208  std::string bytes;
209  serialize_iridium_modem_message(&bytes, msg);
210 
211  std::string sbd_packet;
212  serialize_rudics_packet(bytes, &sbd_packet);
213 
214  if (modem_id_to_imei_.count(msg.dest()))
215  send_sbd_mt(sbd_packet, modem_id_to_imei_[msg.dest()]);
216  else
217  glog.is(WARN) && glog << "No IMEI configured for destination address " << msg.dest()
218  << " so unabled to send SBD message." << std::endl;
219  }
220 }
221 
222 void goby::acomms::IridiumShoreDriver::rudics_send(const std::string& data,
223  goby::acomms::IridiumShoreDriver::ModemId id)
224 {
225  typedef boost::bimap<ModemId, boost::shared_ptr<RUDICSConnection> >::left_map::iterator LeftIt;
226  LeftIt client_it = clients_.left.find(id);
227  if (client_it != clients_.left.end())
228  {
229  glog.is(DEBUG1) && glog << "RUDICS sending bytes: " << goby::util::hex_encode(data)
230  << std::endl;
231  client_it->second->write_start(data);
232  }
233  else
234  {
235  glog.is(WARN) && glog << "Failed to find connection from ModemId " << id << std::endl;
236  }
237 }
238 
239 void goby::acomms::IridiumShoreDriver::rudics_connect(
240  boost::shared_ptr<RUDICSConnection> connection)
241 {
242  connection->line_signal.connect(boost::bind(&IridiumShoreDriver::rudics_line, this, _1, _2));
243  connection->disconnect_signal.connect(
244  boost::bind(&IridiumShoreDriver::rudics_disconnect, this, _1));
245 }
246 
247 void goby::acomms::IridiumShoreDriver::rudics_disconnect(
248  boost::shared_ptr<RUDICSConnection> connection)
249 {
250  typedef boost::bimap<ModemId, boost::shared_ptr<RUDICSConnection> >::right_map::iterator
251  RightIt;
252 
253  RightIt client_it = clients_.right.find(connection);
254  if (client_it != clients_.right.end())
255  {
256  ModemId id = client_it->second;
257  remote_[id].on_call.reset();
258  clients_.right.erase(client_it);
259  glog.is(DEBUG1) && glog << "Disconnecting client for modem id: " << id << "; "
260  << clients_.size() << " clients remaining." << std::endl;
261  }
262  else
263  {
264  glog.is(WARN) &&
265  glog << "Disconnection received from connection we do not have in the clients_ map: "
266  << connection->remote_endpoint_str() << std::endl;
267  }
268 }
269 
270 void goby::acomms::IridiumShoreDriver::rudics_line(const std::string& data,
271  boost::shared_ptr<RUDICSConnection> connection)
272 {
273  glog.is(DEBUG1) && glog << "RUDICS received bytes: " << goby::util::hex_encode(data)
274  << std::endl;
275 
276  try
277  {
278  std::string decoded_line;
279 
280  if (data == "goby\r" ||
281  data == "\0goby\r") // sometimes Iridium adds a 0x00 to the start of transmission
282  {
283  glog.is(DEBUG1) && glog << "Detected start of Goby RUDICS connection from "
284  << connection->remote_endpoint_str() << std::endl;
285  }
286  else if (data == "bye\r")
287  {
288  typedef boost::bimap<ModemId, boost::shared_ptr<RUDICSConnection> >::right_map::iterator
289  RightIt;
290 
291  RightIt client_it = clients_.right.find(connection);
292  if (client_it != clients_.right.end())
293  {
294  ModemId id = client_it->second;
295  glog.is(DEBUG1) && glog << "Detected bye from " << connection->remote_endpoint_str()
296  << " ID: " << id << std::endl;
297  remote_[id].on_call->set_bye_received(true);
298  }
299  else
300  {
301  glog.is(WARN) &&
302  glog << "Bye detected from connection we do not have in the clients_ map: "
303  << connection->remote_endpoint_str() << std::endl;
304  }
305  }
306  else
307  {
308  parse_rudics_packet(&decoded_line, data);
309 
310  protobuf::ModemTransmission modem_msg;
311  parse_iridium_modem_message(decoded_line, &modem_msg);
312 
313  glog.is(DEBUG1) && glog << "Received RUDICS message from: " << modem_msg.src()
314  << " to: " << modem_msg.dest()
315  << " from endpoint: " << connection->remote_endpoint_str()
316  << std::endl;
317  if (!clients_.left.count(modem_msg.src()))
318  {
319  clients_.left.insert(std::make_pair(modem_msg.src(), connection));
320  remote_[modem_msg.src()].on_call.reset(new OnCallBase);
321  }
322 
323  remote_[modem_msg.src()].on_call->set_last_rx_time(goby_time<double>());
324 
325  receive(modem_msg);
326  }
327  }
328  catch (RudicsPacketException& e)
329  {
330  glog.is(DEBUG1) && glog << warn << "Could not decode packet: " << e.what() << std::endl;
331  connection->add_packet_failure();
332  }
333 }
334 
335 void goby::acomms::IridiumShoreDriver::receive_sbd_mo()
336 {
337  try
338  {
339  sbd_io_.poll();
340  }
341  catch (std::exception& e)
342  {
343  glog.is(DEBUG1) && glog << warn << "Could not handle SBD receive: " << e.what()
344  << std::endl;
345  }
346 
347  std::set<boost::shared_ptr<SBDConnection> >::iterator it =
348  mo_sbd_server_->connections().begin(),
349  end = mo_sbd_server_->connections().end();
350  while (it != end)
351  {
352  const int timeout = 5;
353  if ((*it)->message().data_ready())
354  {
355  protobuf::ModemTransmission modem_msg;
356 
357  glog.is(DEBUG1) && glog << "Rx SBD PreHeader: "
358  << (*it)->message().pre_header().DebugString() << std::endl;
359  glog.is(DEBUG1) && glog << "Rx SBD Header: " << (*it)->message().header().DebugString()
360  << std::endl;
361  glog.is(DEBUG1) && glog << "Rx SBD Payload: " << (*it)->message().body().DebugString()
362  << std::endl;
363 
364  std::string bytes;
365  try
366  {
367  parse_rudics_packet(&bytes, (*it)->message().body().payload());
368  parse_iridium_modem_message(bytes, &modem_msg);
369 
370  glog.is(DEBUG1) && glog << "Rx SBD ModemTransmission: "
371  << modem_msg.ShortDebugString() << std::endl;
372 
373  receive(modem_msg);
374  }
375  catch (RudicsPacketException& e)
376  {
377  glog.is(DEBUG1) && glog << warn << "Could not decode SBD packet: " << e.what()
378  << std::endl;
379  }
380  mo_sbd_server_->connections().erase(it++);
381  }
382  else if ((*it)->connect_time() > 0 &&
383  (goby::common::goby_time<double>() > ((*it)->connect_time() + timeout)))
384  {
385  glog.is(DEBUG1) && glog << "Removing SBD connection that has timed out:"
386  << (*it)->remote_endpoint_str() << std::endl;
387  mo_sbd_server_->connections().erase(it++);
388  }
389  else
390  {
391  ++it;
392  }
393  }
394 }
395 
396 void goby::acomms::IridiumShoreDriver::send_sbd_mt(const std::string& bytes,
397  const std::string& imei)
398 {
399  try
400  {
401  using boost::asio::ip::tcp;
402 
403  boost::asio::io_service io_service;
404 
405  tcp::resolver resolver(io_service);
406  tcp::resolver::query query(
407  driver_cfg_.GetExtension(IridiumShoreDriverConfig::mt_sbd_server_address),
408  goby::util::as<std::string>(
409  driver_cfg_.GetExtension(IridiumShoreDriverConfig::mt_sbd_server_port)));
410  tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
411  tcp::resolver::iterator end;
412 
413  tcp::socket socket(io_service);
414  boost::system::error_code error = boost::asio::error::host_not_found;
415  while (error && endpoint_iterator != end)
416  {
417  socket.close();
418  socket.connect(*endpoint_iterator++, error);
419  }
420  if (error)
421  throw boost::system::system_error(error);
422 
423  boost::asio::write(socket, boost::asio::buffer(create_sbd_mt_data_message(bytes, imei)));
424 
425  SBDMTConfirmationMessageReader message(socket);
426  boost::asio::async_read(
427  socket, boost::asio::buffer(message.data()),
428  boost::asio::transfer_at_least(SBDMessageReader::PRE_HEADER_SIZE),
429  boost::bind(&SBDMessageReader::pre_header_handler, &message, _1, _2));
430 
431  double start_time = goby::common::goby_time<double>();
432  const int timeout = 5;
433 
434  while (!message.data_ready() && (start_time + timeout > goby::common::goby_time<double>()))
435  io_service.poll();
436 
437  if (message.data_ready())
438  {
439  glog.is(DEBUG1) && glog << "Tx SBD Confirmation: " << message.confirm().DebugString()
440  << std::endl;
441  }
442  else
443  {
444  glog.is(WARN) && glog << "Timeout waiting for confirmation message from DirectIP server"
445  << std::endl;
446  }
447  }
448  catch (std::exception& e)
449  {
450  glog.is(WARN) && glog << "Could not sent MT SBD message: " << e.what() << std::endl;
451  }
452 }
453 
454 std::string goby::acomms::IridiumShoreDriver::create_sbd_mt_data_message(const std::string& bytes,
455  const std::string& imei)
456 {
457  enum
458  {
459  PRE_HEADER_SIZE = 3,
460  BITS_PER_BYTE = 8,
461  IEI_SIZE = 3,
462  HEADER_SIZE = 21
463  };
464 
465  enum
466  {
467  IEI_MT_HEADER = 0x41,
468  IEI_MT_PAYLOAD = 0x42
469  };
470 
471  static int i = 0;
472  DirectIPMTHeader header;
473  header.set_iei(IEI_MT_HEADER);
474  header.set_length(HEADER_SIZE);
475  header.set_client_id(i++);
476  header.set_imei(imei);
477 
478  enum
479  {
480  DISP_FLAG_FLUSH_MT_QUEUE = 0x01,
481  DISP_FLAG_SEND_RING_ALERT_NO_MTM = 0x02,
482  DISP_FLAG_UPDATE_SSD_LOCATION = 0x08,
483  DISP_FLAG_HIGH_PRIORITY_MESSAGE = 0x10,
484  DISP_FLAG_ASSIGN_MTMSN = 0x20
485  };
486 
487  header.set_disposition_flags(DISP_FLAG_FLUSH_MT_QUEUE);
488 
489  std::string header_bytes(IEI_SIZE + HEADER_SIZE, '\0');
490 
491  std::string::size_type pos = 0;
492  enum
493  {
494  HEADER_IEI = 1,
495  HEADER_LENGTH = 2,
496  HEADER_CLIENT_ID = 3,
497  HEADER_IMEI = 4,
498  HEADER_DISPOSITION_FLAGS = 5
499  };
500 
501  for (int field = HEADER_IEI; field <= HEADER_DISPOSITION_FLAGS; ++field)
502  {
503  switch (field)
504  {
505  case HEADER_IEI: header_bytes[pos++] = header.iei() & 0xff; break;
506 
507  case HEADER_LENGTH:
508  header_bytes[pos++] = (header.length() >> BITS_PER_BYTE) & 0xff;
509  header_bytes[pos++] = (header.length()) & 0xff;
510  break;
511 
512  case HEADER_CLIENT_ID:
513  header_bytes[pos++] = (header.client_id() >> 3 * BITS_PER_BYTE) & 0xff;
514  header_bytes[pos++] = (header.client_id() >> 2 * BITS_PER_BYTE) & 0xff;
515  header_bytes[pos++] = (header.client_id() >> BITS_PER_BYTE) & 0xff;
516  header_bytes[pos++] = (header.client_id()) & 0xff;
517  break;
518 
519  case HEADER_IMEI:
520  header_bytes.replace(pos, 15, header.imei());
521  pos += 15;
522  break;
523 
524  case HEADER_DISPOSITION_FLAGS:
525  header_bytes[pos++] = (header.disposition_flags() >> BITS_PER_BYTE) & 0xff;
526  header_bytes[pos++] = (header.disposition_flags()) & 0xff;
527  break;
528  }
529  }
530 
531  DirectIPMTPayload payload;
532  payload.set_iei(IEI_MT_PAYLOAD);
533  payload.set_length(bytes.size());
534  payload.set_payload(bytes);
535 
536  std::string payload_bytes(IEI_SIZE + bytes.size(), '\0');
537  payload_bytes[0] = payload.iei();
538  payload_bytes[1] = (payload.length() >> BITS_PER_BYTE) & 0xff;
539  payload_bytes[2] = (payload.length()) & 0xff;
540  payload_bytes.replace(3, payload.payload().size(), payload.payload());
541 
542  // Protocol Revision Number (1 byte) == 1
543  // Overall Message Length (2 bytes)
544  int overall_length = header_bytes.size() + payload_bytes.size();
545  std::string pre_header_bytes(PRE_HEADER_SIZE, '\0');
546  pre_header_bytes[0] = 1;
547  pre_header_bytes[1] = (overall_length >> BITS_PER_BYTE) & 0xff;
548  pre_header_bytes[2] = (overall_length)&0xff;
549 
550  glog.is(DEBUG1) && glog << "Tx SBD PreHeader: " << goby::util::hex_encode(pre_header_bytes)
551  << std::endl;
552  glog.is(DEBUG1) && glog << "Tx SBD Header: " << header.DebugString() << std::endl;
553  glog.is(DEBUG1) && glog << "Tx SBD Payload: " << payload.DebugString() << std::endl;
554 
555  return pre_header_bytes + header_bytes + payload_bytes;
556 }
void do_work()
Allows the modem driver to do its work.
void startup(const protobuf::DriverConfig &cfg)
Starts the modem driver. Must be called before poll().
void handle_initiate_transmission(const protobuf::ModemTransmission &m)
Virtual initiate_transmission method. Typically connected to MACManager::signal_initiate_transmission...
double goby_time< double >()
Returns current UTC time as seconds and fractional seconds since 1970-01-01 00:00:00.
Definition: time.h:130
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
Definition: time.h:104
boost::signals2::signal< void(const protobuf::ModemTransmission &message)> signal_receive
Called when a binary data transmission is received from the modem.
Definition: driver_base.h:85
void shutdown()
Shuts down the modem driver.
common::FlexOstream glog
Access the Goby logger through this object.
boost::signals2::signal< void(protobuf::ModemTransmission *msg)> signal_data_request
Called when the modem or modem driver needs data to send. The returned data should be stored in Modem...
Definition: driver_base.h:96
boost::signals2::signal< void(protobuf::ModemTransmission *msg_request)> signal_modify_transmission
Called before the modem driver begins processing a transmission. This allows a third party to modify ...
Definition: driver_base.h:102