I am writting a driver for a GPS unit in linux. I can set on the GPS unit what data it should send out it's ethernet port. The data is arranged in structs of different sizes depending on what it is. For example one packet could be for the GPS solution (containing position, velocity, and orientation data). Another packet could be error information. Regardless, each packet starts with a unique ID. So what I want to do is abitrarily read in all packets from the unit and parse them as they come in depending on the packets unique ID. I am not sure how to do this elegantly though. I don't want to do a switch or else if sequence. So:

if(packet_id == 1)
  parse_gps_solution(packet_data); //function call that parses the data into the correct struct
else if(packet_id == 2)
  parse_gps_error(packet_data);
else if(packet_id == 3)
  parse_raw_gps(packet_data);
etc...

This is not what I want. I am trying to see if there is a way I can elegantly parse using the packet ID itself, I am just not sure how I could do this. I would appreciate any and all brain storming suggestions. Any questions, please ask. Thank you.

-Sam

Edited 4 Years Ago by spetro3387

If all you want to do is avoid the if/else blocks you can implement a function call table that is indexed by the packet_id. Something similar to:

typedef size_t (*gps_fun)(packet_t *, void *);

gps_fun parsers[] = {
    0,
    parse_gps_solution, // packet id == 1
    parse_gps_error,    // packet id == 2
    ...
};


/* 
 * The parsers would do all the work 
 * returning the total number of bytes parsed
 */
size_t parse_gps_solution (packet_t *raw_packet, void * out_struct) {
   out_struct = (solution_struct_t *)raw_packet; 
   return sizeof(solution_struct_t);
}


/*
 * Now you can just do something like
 */
generic_gps_struct_t gps;
size_t bytes = parsers[packet_id](packet, &gps);

If you do not want to first parse the packet id out as an alternate step then you can certainly just use the generic_gps_struct_t to contain the initial required field and enough room for any gps packet. Something like:

struct generic_gps_struct_t {
   int packet_id;
   char payload[LARGEST_GPS_PACKET_SIZE];
};

And then you could cast the raw packet to the generic type and use the packet_id field to index into the function table.

As always, when dealing with network data you have to be aware of endianess.

Thanks for the reply. I actually had thought of this, but it still requires a listing of all the functions (in the array initialization). I don't want to make it seem like I am being lazy, just curious really. I was wondering if there was some trickery that I didn't know of that could be even more direct. Using the ID to directly call the function. It turns out that some ID's are totaly different from the others. The list is like 1, 2, 3, 7, 14, 15, 20, 21, 22, 10001, 1007, 10008. The method you suggest would actually need a hash table. Probably best to just use a switch or else/if sequence. Not unless there is some other trickery.

-Sam

enum or define of possible values and switch no ?

#include <stdio.h>

enum eGprsEnum
{
   GPRS_T = 3,
   GPRS_X = 1000,
};

int iDoStuff(int iId)
{
   int iRet = 0;

   switch(iId)
   {
   case GPRS_T:
      printf("%d",iId);
      break;
   case GPRS_X:
      break;
   default:
      iRet = -1;
      break;
   }

   return iRet;
}

int main()
{
   iDoStuff(3);
   return 0;
}

Edited 4 Years Ago by Sokurenko: more

Parsing, in general, is messy. For each tyoe of message you support (no matter how similar in size) you will need code to address that particular message. The most logical encapsulation for this behavior is a function but in order to call the function at runtime based on the type you need to either have a jump table (as I described) or some variant of a switch statement. It would be wonderful if the packet itself could contain a function pointer to the callback that handled this packet type but that is obviously not possible across machines (or even processes).
What you are left with is cluncky at some level.

This question has already been answered. Start a new discussion instead.