Packer Documentation

Maintained by: David Austin

1.0 Introduction
The Packer library (part of GeneralComms) provides an interface for packing messages into the standard message format. It is recommended that you use the packer or a similar interface which hides the details of the message format so that your code won't be broken (and lost in the mists of time) if the message format has to change.

The Packer is imperfect but it serves the purpose. I wanted to avoid any two-stage compilation process which commonly occurs with other packages. I may have to reconsider this decision and introduce some better (more complete) format for describing (and particularly documenting) the messages. This format can then be processed to generate the DROSMessages header file.

2.0 Packer Tutorials

2.1 Packing a Simple Message

int SendAck(GCClient *c, int id, int status) {
  AckMsg ack;
  unsigned int size;
  if (pack_msg_hdr_all((char *) &ack, sizeof(ack), Ack,
		       LITTLE_ENDIAN_ENCODING, id) < 0) {
    DROS_DEBUG(3, "Failed to pack_msg_hdr_all");
    return -1;
  }

  if (pack_msg_data((char *) &ack, sizeof(ack), NULL, 
		    ACK_FMT_STR, status) < 0) {
    DROS_DEBUG(3, "Failed to pack_msg_data");
    return -1;
  }

  size = finalise_msg((char *) &ack, sizeof(ack));
  if (size < 0) {
    DROS_DEBUG(3, "Failed to finalise_msg");
    return -1;
  }

  DROS_DEBUG(600, "sending to " << c->GetFd() << " ... ");
  if (c->Send(size, (char *) &ack) < (int) size) {
    DROS_DEBUG(3, "error sending to client - disconnecting it");
    c->Close();
    return -1;
  }
  DROS_DEBUG(600, " sent");

  return 0;
}

This example shows how to pack a simple message with just an integer in the body. ACK_FMT_STR is defined as "%i".

2.2 Unpacking a Simple Message

int HandleReceive(GCEventLoop *loop, GCServer *s,
	          GCChannel *c, int event, long id, 
		  void *userdata) {
  const char *dros_function_name = "HandleReceive";
  unsigned int mtype;
  unsigned int msgid;
  int ack;

  if (check_checksum(c->GetBufPtr(), c->DataPending()) < 0) {
    DROS_DEBUG(2, "Received bogus message, dropping it");
    return GC_HANDLER_EXCLUSIVELYMINE;
  }
  
  if (extract_msg_hdr_all(c->GetBufPtr(), c->DataPending(), &mtype,
			  NULL, NULL, &msgid) < 0) {
    DROS_DEBUG(2, "Failed to extract message type!!, dropping it");
    return GC_HANDLER_EXCLUSIVELYMINE;
  }

  DROS_DEBUG(500, "Received message of type " << MessageTypeToString(mtype)
	     << " len = " << c->DataPending());  

  if (mtype == Ack) {
    if (extract_msg_data(c->GetBufPtr(), c->DataPending(), NULL,
			 ACK_FMT_STR, &ack) == 1) {
      DROS_DEBUG(150, "Got ack message " << ack);
    }
    break;
  }
  return GC_HANDLER_EXCLUSIVELYMINE;
}

This example shows how to unpack a simple message with just an integer in the body. ACK_FMT_STR is defined as "%i".

2.3 Packing a Variable-Length Message

  char *dataMsg;

  dataMsg = (char *) malloc(sizeof(LaserServerNormalScanIsMsg));

  if (pack_msg_hdr_all(dataMsg_, sizeof(LaserServerNormalScanIsMsg),
	  	       LaserServerNormalScanIs, LITTLE_ENDIAN_ENCODING, 
                       0) < 0) {
    DROS_DEBUG(3, "Failed to pack_msg_hdr_all");
    return -1;
  }
  

  unsigned int i;
  unsigned int offset;
  offset = 0;


  if (pack_msg_data(dataMsg, sizeof(LaserServerNormalScanIsMsg),
	  	    &offset, LASERSERVERNORMALSCANIS_FMT_STR, 
		    GetCurrentTime()) < 0) {
    DROS_DEBUG(3, "Failed to pack_msg_data");
    return -1;
  }

  for (i = 0; i < 361; i++) {
    if (pack_msg_data(dataMsg, sizeof(LaserServerNormalScanIsMsg),
		      &offset, LASERSERVERNORMALSCANIS_FMT_STR1,
		      laser->GetReading(i)) < 0) {
      DROS_DEBUG(3, "Failed to pack_msg_data");
      return -1;
    }
  }
  
  int size;
  size = finalise_msg(dataMsg, sizeof(LaserServerNormalScanIsMsg));
  if (size < 0) {
    DROS_DEBUG(3, "Failed to finalise_msg");
    return -1;
  }

  /* Now send the message */

This example shows the use of the offset parameter to the pack_msg_data function. The variable offset maintains the current packing position. It must be initialised to zero and is then updated by pack_msg_data as packing occurs. Note: for fixed length messages, it is not necessary to maintain the offset so NULL can be passed in place of a pointer to a variable to update.

2.4 Unpacking a Variable-Length Message

int HandleReceive(GCEventLoop *loop, GCServer *s,
	          GCChannel *c, int event, long id, 
		  void *userdata) {
  const char *dros_function_name = "HandleReceive";
  unsigned int mtype;
  unsigned int msgid;
  int ack;

  if (check_checksum(c->GetBufPtr(), c->DataPending()) < 0) {
    DROS_DEBUG(2, "Received bogus message, dropping it");
    return GC_HANDLER_EXCLUSIVELYMINE;
  }
  
  if (extract_msg_hdr_all(c->GetBufPtr(), c->DataPending(), &mtype,
			  NULL, NULL, &msgid) < 0) {
    DROS_DEBUG(2, "Failed to extract message type!!, dropping it");
    return GC_HANDLER_EXCLUSIVELYMINE;
  }

  DROS_DEBUG(500, "Received message of type " << MessageTypeToString(mtype)
	     << " len = " << c->DataPending());  

  if (mtype != LaserServerNormalScanIs) 
    return GC_HANDLER_NOTMINE;

  unsigned int i;
  unsigned int offset = 0;
  double timeStamp;
  double r[361];

  if (extract_msg_data(c->GetBufPtr(), c->DataPending(), &offset,
		       LASERSERVERNORMALSCANIS_FMT_STR, &timeStamp) < 1) {
    DROS_DEBUG(2, "Warning: something went wrong with "
	       "LaserServerNormalScanIs msg");
    return GC_HANDLER_EXCLUSIVELYMINE;
  }
  
  DROS_DEBUG(2000, "Received new data " << timeStamp_);

  for (i = 0; i < 361; i++) {
    if (extract_msg_data(c->GetBufPtr(), c->DataPending(), &offset,
		       LASERSERVERNORMALSCANIS_FMT_STR1, &(r[i])) < 1) {
      DROS_DEBUG(2, "Warning: something went wrong with "
		 "LaserServerNormalScanIs msg " << i);
      return GC_HANDLER_EXCLUSIVELYMINE;
    }
  }

  DROS_DEBUG(5000, "Front range = " << r[180]);
  return GC_HANDLER_EXCLUSIVELYMINE;
}

This example shows the use of the offset parameter to the extract_msg_data function. The variable offset maintains the current unpacking position. It must be initialised to zero and is then updated by extract_msg_data as unpacking occurs. Note: for fixed length messages, it is not necessary to maintain the offset so NULL can be passed in place of a pointer to a variable to update.

3.0 Packer Format Flags
The packer routines obey the same format strings as printf(3) in general with a few diffrences.

Characters in the format string which do not form part of a specifier are copied to the message buffer. Specifiers start with a '%' character, have a number of options or flags and end with a single character which describes the format.

Major differences from printf(3) are:

  1. Numbers (ints, doubles, etc) are not converted to strings of digits from the set {'0'...'9','.','+','-'...}. Instead, the data is copied into the appropriate binary format according to the encoding field of the message header that is being filled in.
  2. When strings are copied, the trailing NUL character is copied by default.
Copyright 2001,2002 David Austin