Writing encoder and decoder routines

Updated: April 19, 2023

After writing the constructors and the methods for creating instances and for copying data between instances and data samples, you can compile the C++ type unit. However, the new data type is of limited use because it can't encode or decode data. Any encoder or decoder routine not defined for a data type uses a default implementation, which simply sets the error code to ENOSYS. Thus, the next step is to write encoder and decoder handlers.

In our example, we want to support PPS and CSTRUCT encoding of data samples containing DataBuffer updates.

PPS encoding

The contents of the data buffer can be expressed as a PPS string by encoding it as a Base64 string. To enable PPS encoding, we override the encodePPS() and decodePPS() functions in the DataBufferType derived class:
int DataBufferType::encodePPS( const TypeInstance* instance, 
                               const char* name, 
                               char* buffer[], 
                               const size_t buffer_size )
{
    int rc = -1;

    assert( name );
    if ( !buffer ) {
        errno = EINVAL;
    }
    else if ( !instance ) {
        errno = ENODATA;
    }
    else {
        const DataBuffer* sample = ( const DataBuffer* )instance->getData();

        if ( !sample ) {
            errno = EIO;
        }
        else {
            size_t data_size = sample->buffer().size();
            const unsigned char* data = sample->buffer().data();
            size_t required_size = sizeof( "\n:b64:\n" ) + 
                                           strlen( name ) + 
                                           sample->name().size();

            required_size += pips_encode_base64( data, data_size, NULL, 0 );

            if ( *buffer && required_size > buffer_size ) {
                errno = ENOBUFS;
            }
            else {
                if ( !(*buffer) ) {
                    *buffer = new char[ required_size ];
                }
                if ( *buffer ) {
                    int pos;
                    strlcpy( *buffer, name, required_size );
                    strlcat( *buffer, "\n", required_size );
                    strlcat( *buffer, sample->name().c_str(), required_size );
                    pos = strlcat( *buffer, ":b64:", required_size );
                    if ( 0 <= pips_encode_base64( data, 
                                                  data_size, 
                                                  *buffer + pos, 
                                                  required_size - pos ) ) {
                        rc = strlcat( *buffer, "\n", required_size );
                    }
                }
            }
        }
    }
    return rc;
}

int DataBufferType::decodePPS( const pips_data_t data, 
                               const size_t data_size, 
                               fastrtps_type_instance_t** instance, 
                               const pips_guid_t* guid )
{
    int rc = -1;
    if ( !data ) {
        errno = ENODATA;
    }
    else if ( !instance ) {
        errno = EINVAL;
    }
    else {
        char* workspace = new char[ data_size + 2 ];

        if ( workspace ) {
            pps_decoder_t decoder;
            int bytes_decoded = strlcpy( workspace, data, data_size + 2 );

            // Ensure that the PPS data is terminated by a newline character.
            strlcat( workspace, "\n", data_size + 2 );
            if ( PPS_DECODER_OK != pps_decoder_initialize( &decoder, workspace ) ) {
                errno = EIO;
            }
            else if ( PPS_TYPE_OBJECT == pps_decoder_type( &decoder, NULL )
                  && PPS_DECODER_OK != pps_decoder_push( &decoder, NULL ) ) {
                errno = EIO;
            }
            else {
                const char* name = pps_decoder_name( &decoder );
                if ( !name ) {
                    errno = EIO;
                }
                else {
                    *instance = createInstance();
                    if ( *instance ) {
                        pps_decoder_state_t state;
                        DataBuffer* sample = ( DataBuffer* )(*instance)->getData();
                        if ( sample ) {
                            sample->name( name );
                            // Decode the base64 string into binary data.
                            pps_decoder_get_state( &decoder, &state );
                            if ( !state.node ) {
                                errno = EIO;
                            }
                            else {
                                uint8_t* buffer;
                                int buffer_size;
                                const char* b64_data = state.node->value.str;
                                size_t b64_data_size = strlen( b64_data );

                                buffer_size = pips_decode_base64( 
                                                            b64_data, b64_data_size, NULL, 0 );
                                buffer = buffer_size > 0 ? new uint8_t[ buffer_size ] : NULL;
                                if ( buffer ) {
                                    pips_decode_base64( 
                                                b64_data, b64_data_size, buffer, buffer_size );
                                    sample->buffer().assign( buffer, buffer+buffer_size );
                                    rc = bytes_decoded;
                                    delete buffer;
                                }
                            }
                        }
                    }
                }
            }
            delete workspace;
        }
    }
    return rc;
}

CSTRUCT encoding

To support CSTRUCT encoding, we need a suitable C structure into which sample data can be deserialized. The data type generated by fastrtpsgen is a C++ class so it's not suitable for the C API of PiPS. A compatible structure that mirrors the generated one can be defined:
typedef struct fastrtps_databuffer {
    const char*     name;
    uint16_t        size;
    const uint8_t*  buffer;
} fastrtps_databuffer_t;

We specify the fastrtps_databuffer structure in a header file that has a path of pips/types/databuffer.h. This C structure defines a member for each field of the structure in DataBuffer.idl.

It's preferable that data samples returned by the decoder functions are allocated as a contiguous buffer because this way, clients can deallocate those samples simply by calling free() without having to know if individual members need to be deep-freed.

To serialize sample data into a fastrtps_databuffer buffer, we override the encodeCSTRUCT() function:
int DataBufferType::encodeCSTRUCT( const TypeInstance* instance, 
                                   const char* name __attribute__((__unused__)), 
                                   char* buffer[], 
                                   const size_t buffer_size )
{
    int rc = -1;

    if ( !buffer ) {
        errno = EINVAL;
    }
    else if ( !instance ) {
        errno = ENODATA;
    }
    else {
        size_t data_size;
        const DataBuffer* sample = ( const DataBuffer* )instance->getData();

        assert( sample );
        // Calculate the size of the buffer required to hold the data.
        data_size = sizeof( fastrtps_buffer_t ) + 
                            sample->name().length() + 1 + 
                            sample->buffer().size();
        if ( *buffer && buffer_size < data_size ) {
            errno = ENOBUFS;
        }
        else {
            if ( !*buffer ) {
                *buffer = new char[ data_size ];
            }
            if ( *buffer ) {
                int off;
                const uint8_t* data = sample->buffer().data();
                fastrtps_buffer_t* cstruct_data = ( fastrtps_buffer_t* )*buffer;
                cstruct_data->name = *buffer + sizeof( fastrtps_buffer_t );
                off = strlcpy( cstruct_data->name, 
                               sample->name().c_str(), 
                               sample->name().length() + 1 );
                cstruct_data->buffer_size = data_size - 
                                            sizeof( fastrtps_buffer_t ) - 
                                            sample->name().length() - 1;
                cstruct_data->buffer = ( uint8_t* )cstruct_data->name + off + 1;
                memcpy( cstruct_data->buffer, data, sample->buffer().size() );
                rc = data_size;
            }
        }
    }
    return rc;
}
To deserialize data from a fastrtps_databuffer structure into sample data, we must override decodeCSTRUCT():
int DataBufferType::decodeCSTRUCT( const pips_data_t data, 
                                   const size_t data_size, 
                                   fastrtps_type_instance_t** instance, 
                                   const pips_guid_t* guid )
{
    int rc = -1;

    if ( !data ) {
        errno = ENODATA;
    }
    else if ( !instance ) {
        errno = EINVAL;
    }
    else {
        if ( !*instance ) {
            *instance = createInstance();
        }
        if ( *instance ) {
            DataBuffer* sample = ( DataBuffer* )(*instance)->getData();
            if ( !sample ) {
                errno = EIO;
            }
            else {
                sample->buffer().clear();
                sample->buffer().assign( data, data+data_size );
                rc = sample->buffer().size();
            }
        }
    }
    return rc;
}