This blog post attempts to provide advice about re-using items from a decoded message in a subsequent encoding of a different message.

Let's look at the employee sample in the c/sample_ber/employee directory of the ASN1C SDK. The ASN.1 specification for this sample is fairly simple and looks like this:

Employee DEFINITIONS ::= BEGIN
EXPORTS;

PersonnelRecord ::= [APPLICATION 0] IMPLICIT SET {
   Name,
   title [0] IA5String,
   number EmployeeNumber,
   dateOfHire [1] Date,
   nameOfSpouse [2] Name,
   children [3] IMPLICIT SEQUENCE OF ChildInformation
}

ChildInformation ::= SET {
   Name,
   dateOfBirth [0] Date
}

Name ::= [APPLICATION 1] IMPLICIT SEQUENCE {
   givenName IA5String,
   initial IA5String,
   familyName IA5String
}

EmployeeNumber ::= [APPLICATION 2] IMPLICIT INTEGER

Date ::= IA5String

END

Now, suppose the ChildInformation piece and the Name piece need to be used in a different message, called a FamilyRecord, that is going to be encoded after a PersonnelRecord message is decoded. We can change the ASN.1 specification that defines the PersonnelRecord so it looks like this:

Employee DEFINITIONS AUTOMATIC TAGS ::= BEGIN

EXPORTS;

IMPORTS Name, ChildInformation FROM Common;

PersonnelRecord ::= SET {
   employeeName Name,
   title IA5String,
   number EmployeeNumber,
   dateOfHire Date,
   nameOfSpouse Name,
   children SEQUENCE OF ChildInformation
}

EmployeeNumber ::= INTEGER

Date ::= IA5String

END

So we can see now that instead of defining ChildInformation and Name, the specification imports them from a module named Common. The other changes are that we are using an explicit element name called employeeName just to make things neater, and we are using AUTOMATIC TAGS for sanity preservation.

The Common module would look like this:

Common DEFINITIONS AUTOMATIC TAGS ::= BEGIN

EXPORTS ChildInformation, Name;

ChildInformation ::= SET {
   childName Name,
   dateOfBirth Date
}

Name ::= SEQUENCE {
   givenName IA5String,
   initial IA5String,
   familyName IA5String
}

END

And we also need to create a specification that defines FamilyRecord:

Family DEFINITIONS AUTOMATIC TAGS ::= BEGIN

EXPORTS;

IMPORTS Name, ChildInformation FROM Common;

FamilyRecord ::= SET {
nameOfSpouse Name,
ageOfSpouse INTEGER (18..MAX),
children SEQUENCE OF ChildInformation
}

END

So we have a module named Common that defines ChildInformation and Name. And we have two other modules, named Employee (which defines the PersonnelRecord PDU) and Family (which defines the FamilyRecord PDU) that both make use of these two definitions in the Common module.

Now, suppose we have a need to write some C code that decodes a PersonnelRecord and uses the name of the employee's spouse and and the information about the employee's children in a new encoding of a FamilyRecord. Below is a complete C program that can accomplish this. The sections that I'm going to talk about in a little more detail are indicated with numbers in square brackets, e.g., [1], [2], [3], etc.

/*
This program does the following:
Reads and decodes an already-encoded PersonnelRecord message.
Uses pieces of that decoded PersonnelRecord to populate the structures for a new FamilyRecord message.
Encodes that FamilyRecord message.
*/

#include "Employee.h"
#include "Family.h"
#include "rtxsrc/rtxDiag.h"
#include "rtxsrc/rtxFile.h"

#define MAXMSGLEN (1024)

int main()
{
   PersonnelRecord tEmployee;
   FamilyRecord tFamily;
   OSCTXT tDecodeContext, tEncodeContext;

   /* Receives the encoded (i.e., not yet decoded) PersonnelRecord message from the message.dat file */
   OSOCTET* pachEmployeeMessage;

   /* Receives the encoded FamilyRecord message from this program's encode call */
   OSOCTET achFamilyMessage[MAXMSGLEN];

   /* Receives a pointer to the encoded FamilyRecord message in order to print it and then write it to a file */
   OSOCTET *pachFamilyMessage;

   OSSIZE iLength;
   int iStatus;
   FILE* ptOutputFile;
   const char szInputFileName[] = "EmployeeMessage.dat";
   const char szOutputFileName[] = "FamilyMessage.dat";
   OSBOOL bTrace = TRUE, bVerbose = FALSE;

   /* Initialize the context structure for the decoding. */
   if (rtInitContext (&tDecodeContext) != 0) { /* [1] */
      printf ("Error initializing decode context\n");
      return -1;
   }
   rtxSetDiag (&tDecodeContext, bVerbose);

   /* Read the input file into a memory buffer. */
   iStatus = rtxFileReadBinary (&tDecodeContext, szInputFileName, &pachEmployeeMessage, &iLength); /* [2] */
   if (0 != iStatus) {
      printf ("Error opening %s for read access\n", szInputFileName);
      return -1;
   }
   iStatus = xd_setp64 (&tDecodeContext, pachEmployeeMessage, iLength, 0, 0, 0);
   if (0 != iStatus) {
      rtxErrPrint (&tDecodeContext);
      return iStatus;
   }

   /* Clear the structures that will receive the decoded message. */
   asn1Init_PersonnelRecord (&tEmployee);

   /* Decode the PersonnelRecord message. */
   iStatus = asn1D_PersonnelRecord (&tDecodeContext, &tEmployee, ASN1EXPL, 0); /* [3] */
   if (0 == iStatus) {
      if (bTrace) {
         printf ("Decode of PersonnelRecord was successful\n");
         printf ("Decoded record:\n");
         asn1Print_PersonnelRecord ("Employee", &tEmployee);
      }
   }
   else {
      printf ("decode of PersonnelRecord failed\n");
      rtxErrPrint (&tDecodeContext);
      return -1;
   }

   /* Now use the spouse's name and the children's names in a new FamilyRecord message. */

   /* Initialize the context structure for the encoding. */
   iStatus = rtInitContext (&tEncodeContext); /* [4] */
   if (0 != iStatus) {
      printf ("encoding context initialization failed\n");
      rtxErrPrint (&tEncodeContext);
      return iStatus;
   }
   rtxSetDiag (&tEncodeContext, bVerbose);

   /* Populate the structures for the FamilyRecord message. */ /* [5] */
   tFamily.nameOfSpouse = tEmployee.nameOfSpouse;
   tFamily.ageOfSpouse = 30;
   tFamily.children = tEmployee.children;

   /* Encode the FamilyRecord message. */
   xe_setp (&tEncodeContext, achFamilyMessage, sizeof(achFamilyMessage));
   if ((iLength = asn1E_FamilyRecord (&tEncodeContext, &tFamily, ASN1EXPL)) > 0) /* [6] */
   {
      pachFamilyMessage = xe_getp (&tEncodeContext);
      if (bTrace) {
         if (XU_DUMP (pachFamilyMessage) != 0)
         printf ("dump of ASN.1 message failed.");
      }
   }
   else {
      rtxErrPrint (&tEncodeContext);
      return iLength;
   }

   /* Write the encoded message out to the output file */ /* [7] */

   if (0 != (ptOutputFile = fopen (szOutputFileName, "wb"))) {
      fwrite (pachFamilyMessage, 1, iLength, ptOutputFile);
      fclose (ptOutputFile);
   }
   else {
      printf ("Error opening %s for write access\n", szOutputFileName);
      return -1;
   }

   /* Now free up our contexts. */ /* [8] */
   rtFreeContext (&tDecodeContext);
   rtFreeContext (&tEncodeContext);

   return 0;
}

In part [1] we're initializing a context structure for decoding.

In part [2] we're reading a file that contains an encoded PersonnelRecord. The byte array pachEmployeeMessage will have the bytes of the encoded message.

In part [3] we're decoding the PersonnelRecord into the tEmployee structure.

In part [4] we're initializing a context structure for encoding. Note that we're using different context structures for decoding and encoding.

Part [5] is the crucial part. Here we're populating the members of the tFamily structure before we use it to encode a FamilyRecord message. For two of those members we're using members of the tEmployee structure, which contains the decoded information from the PersonnelRecord message. In both cases the members are structures in the generated C code, so the assignment results in a shallow copy of the structure from tEmployee to tFamily. So all pointers within the tEmployee structures stay the same in the tFamily structures. The crucial part to remember here is that the memory used for the decoding of the PersonnelRecord message (i.e., the tEmployee structure) must remain intact until we're completely done with the tFamily structure, since the tFamily structure now has pointers to that memory.

In part [6] we're encoding a FamilyRecord message using the tFamily structure that we just populated in part [5].

In part [7] we're writing the encoded FamilyRecord message out to a file.

In part [8] we're freeing the two contexts that we used, one for decoding and one for encoding. As pointed out in part [5] it's crucial that the context, and hence the memory, used for the decoding remain intact until we're completely done with the encoding, since the structure used for the encoding has pointers to the memory used for the decoding.


Published

Category

ASN1C