views:

233

answers:

2

I want to know how to read a struct within a struct via php's unpack function. When I get an IS_MCI packet, I check it's Type to make sure it's equal to ISP_MCI, and then I check NumC to find out how many CompCar structs there are within this packet. The problem is trying to unpack these contents into an array via a single function. I always get a undefined offset. So, i'm looking for some fresh eyes on the matter.

How would you handle this packet?

The struct in question is this:

struct IS_MCI // Multi Car Info - if more than 8 in race then more than one of these is sent
{
    byte    Size;       // 4 + NumC * 28
    byte    Type;       // ISP_MCI
    byte    ReqI;       // 0 unless this is a reply to an TINY_MCI request
    byte    NumC;       // number of valid CompCar structs in this packet

    CompCar Info[8];    // car info for each player, 1 to 8 of these (NumC)
};

struct CompCar // Car info in 28 bytes - there is an array of these in the MCI (below)
{
    word    Node;       // current path node
    word    Lap;        // current lap
    byte    PLID;       // player's unique id
    byte    Position;   // current race position : 0 = unknown, 1 = leader, etc...
    byte    Info;       // flags and other info - see below
    byte    Sp3;
    int     X;          // X map (65536 = 1 metre)
    int     Y;          // Y map (65536 = 1 metre)
    int     Z;          // Z alt (65536 = 1 metre)
    word    Speed;      // speed (32768 = 100 m/s)
    word    Direction;  // direction of car's motion : 0 = world y direction, 32768 = 180 deg
    word    Heading;    // direction of forward axis : 0 = world y direction, 32768 = 180 deg
    short   AngVel;     // signed, rate of change of heading : (16384 = 360 deg/s)
};
+1  A: 
$msg = 
   chr(0x20) // Size = 32 (4+1*28)
  . chr(0x1) // Type = 1
  . chr(0x0) // ReqI=0
  . chr(0x1) // NumC=1
    . chr(0x1) . chr(0x0) // node=1
    . chr(0x2) . chr(0x0)  // lap=2
    . chr(0x3) // puid=3
    . chr(0x5) // pos=5
    . chr(0x10) // info=16
    . chr(0x0) //sp3=0
    . chr(0x0) . chr(0x0) . chr(0x1) . chr(0x0) // x=65536
    . chr(0x0) . chr(0x0) . chr(0x2) . chr(0x0) // y=65536*2
    . chr(0x0) . chr(0x0) . chr(0x3) . chr(0x0)  // z=65536*3
    . chr(0x0) . chr(0x20) // speed=8192
    . chr(0x0) . chr(0x10) // dir=4096
    . chr(0x0) . chr(0x8) // heading=2048
    . chr(0x0) . chr(0x4) // AngVel=1024
;

$IS_MCI = unpack('CSize', $msg);
if ( strlen($msg) < $IS_MCI['Size'] ) {
  die("not enough data");
}
$IS_MCI += unpack('CType/CReqI/CNumC', substr($msg, 1));
$IS_MCI['Info'] = array();

for($i=0; $i<$IS_MCI['NumC']; $i++) {
  $data = substr($msg, 4+($i*28), 28);
  $IS_MCI['Info'][] = unpack('vNode/vLap/CPLID/CPosition/CInfo/CSp3/lX/lY/lZ/vSpeed/vDirection/vHeading/sAngVel', $data);
}
print_r($IS_MCI);

prints

Array
(
    [Size] => 32
    [Type] => 1
    [ReqI] => 0
    [NumC] => 1
    [Info] => Array
        (
            [0] => Array
                (
                    [Node] => 1
                    [Lap] => 2
                    [PLID] => 3
                    [Position] => 5
                    [Info] => 16
                    [Sp3] => 0
                    [X] => 65536
                    [Y] => 131072
                    [Z] => 196608
                    [Speed] => 8192
                    [Direction] => 4096
                    [Heading] => 2048
                    [AngVel] => 1024
                )

        )

)

Now, that code makes some assumptions that you might not want to take for granted (i.e. add a lot more error/read-data handling).

  • It assumes the packet ($msg) has been completely read before the code runs. You might want to read only the parts you currently need (no need for substr() then). Or least be prepared that the message can arrive in several chunks.
  • It also takes the size/num parameters for granted, i.e. it doesn't check if the values are feasible and enough data is available. That's definitely something you have to change. Size must be between 0...228, NumC must be between 0...8 and both values must fit together and so on.
  • Also take a closer look at the format identifiers I've used in unpack(). For wordI've used v which stands for "unsigned short (always 16 bit, little endian byte order). But for int I've used l: "signed long (always 32 bit, machine byte order)". That's ok on my machine. But search the documentation of the protocol for the endianness of the data.

The testdata in $msg has been taken from the result of

__declspec(align(1)) struct CompCar // Car info in 28 bytes - there is an array of these in the MCI (below)
{
    word    Node;       // current path node
    word    Lap;        // current lap
    byte    PLID;       // player's unique id
    byte    Position;   // current race position : 0 = unknown, 1 = leader, etc...
    byte    Info;       // flags and other info - see below
    byte    Sp3;
    int     X;          // X map (65536 = 1 metre)
    int     Y;          // Y map (65536 = 1 metre)
    int     Z;          // Z alt (65536 = 1 metre)
    word    Speed;      // speed (32768 = 100 m/s)
    word    Direction;  // direction of car's motion : 0 = world y direction, 32768 = 180 deg
    word    Heading;    // direction of forward axis : 0 = world y direction, 32768 = 180 deg
    short   AngVel;     // signed, rate of change of heading : (16384 = 360 deg/s)
};

__declspec(align(1)) struct IS_MCI // Multi Car Info - if more than 8 in race then more than one of these is sent
{
    byte    Size;       // 4 + NumC * 28
    byte    Type;       // ISP_MCI
    byte    ReqI;       // 0 unless this is a reply to an TINY_MCI request
    byte    NumC;       // number of valid CompCar structs in this packet

    CompCar Info[1];    // example: one element, fixed
};

int _tmain(int argc, _TCHAR* argv[])
{
  struct IS_MCI mci = {
    32, 1, 0, 1,
    { 1, 2, 3, 5, 16, 0, 65536, 65536*2, 65536*3, 8192, 4096, 2048, 1024 }
  };

  WSADATA wsaData;
  WORD wVersionRequested = MAKEWORD( 2, 2 );
   int err = WSAStartup( wVersionRequested, &wsaData );
  if ( err != 0 ) {
      /* Tell the user that we could not find a usable */
      /* WinSock DLL.                                  */
      return 1;
  }

  SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  sockaddr_in addr; 
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
  addr.sin_port = htons( 8081 );
  if ( 0!=connect( s, (SOCKADDR*) &addr, sizeof(addr) ) ) {
    printf("%X ", WSAGetLastError());
    return 0;
  }
  send(s, (const char*)&mci, sizeof(mci), 0);
  shutdown(s, SD_BOTH);
  closesocket(s);
  return 0;
}
VolkerK
Thank you, I will test and get back to you. Thank you sir.
Mark Tomlin
+1  A: 
Misiek
I'm taking a slightly different route, but I approve this post! +1.
Mark Tomlin