# Serialization

Storm has a library that provides serialization facilities for objects. These facilities are implemented by the classes `core.io.ObjIStream` and `core.io.ObjOStream`, which specify the binary format and provide the low-level operations required to implement serialization. These classes are complemented by a serialization library written in Basic Storm, that generates the members `read` and `write` for the classes that should be serialized.

The format used for serialization contains enough information to inspect it without having access to the original type information; the needed type information is stored inside the serialization stream. The class `util.serialize.TextObjStream` can be used to inspect and pretty-print previously serialized objects.

The serialization format is designed to allow serializing multiple objects inside a single stream while minimizing overhead. Therefore, type information is only sent once in one session (represented by an instance of `ObjIStream` or `ObjOStream` respectively), and thus care needs to be taken to ensure that the encoder and decoder use the same length of sessions. For example, if the encoder writes two objects using one instance of `ObjOStream`, but the reader reads the two objects using different instances of `ObjIStream`, deserialization will most likely fail.

## Serializing Data

Using the serialization is fairly easy. All serializable types provide a `write` and a `read` function that handles serialization and deserialization respectively. They shall have the following signatures (`T` is the type being serialized):

- `void write(T this, ObjOStream to)`: The `write` function is a member function of `T`. It writes the entire object recursively to the stream.
- `T read(ObjIStream from)` : The `read` function is a static member function of `T` that reads an instance of `T` from the stream and returns the created instance.

Both `ObjOStream` and `ObjIStream` have a constructor that takes a single `IStream` or `OStream`, and they are thus easily created. As mentioned above, these object streams keep track of state associated with the serialization in order to not duplicate data in the stream, and to keep the serialized data stream intact. Therefore, if multiple objects are written using one `OStream` instance, they have to be read back using one `IStream` instance. Conversely, if multiple objects are written using multiple `OStream` instances, they have to be read back using multiple `IStream` instances.

The serialization handles inheritance, shared instances and cyclic structures, just like the clone mechanism does. This means an object that contains (directly or indirectly) two references to the same object will be serialized and deserialized just like expected, each distinct object is serialized once, and all references that were originally referring to the same object will still refer to the same object in the deserialized object. The same is true for structures containing cycles. Each object serialized at the top-level (i.e. the first call to `write` or `read`) are considered separate entities in this regard, meaning that two top-level objects will not share any data with each other, even if they have some objects in common. This means that reading two objects from a single `ObjIStream` at top-level guarantees that the two object hierarchies are disjoint (both of them are, of course, also disjoint with the original, serialized object, which may no longer exist).

As mentioned earlier, it is possible to inspect serialized data in textual format using the class `util.serialize.TextObjStream`. This object works much like `ObjIStream`, but instead of producing objects, it produces human readable text. For this stream, just like with `ObjIStream`, it is important to match the number of instances when reading with the number of instances when writing.

## Limiting Impact of Untrusted Data

To provide basic protection against deserialization of untrusted data, it is possible to impose various limits on the size of the deserialized data. The `ObjIStream` provides the following variables for this purpose:

- `maxReadSize`: Limits the total size of each object hierarchy that is read through a top-level call to `read`. This number accounts for all objects and arrays created by the deserialized data. However, it does not account for all forms of overhead (e.g. object headers, extra memory used for hash tables, etc.). As such, do not assume that the limit corresponds to an exact number of bytes.
- `maxArraySize`: Limits the number of array allocations. Array allocations could be particularly problematic since ambiguous references causes the garbage collector to retain a small number of objects that could be reclaimed. Large array allocations are more likely to be pinned in this manner, and the impact of retaining them are quite significant. This is why the array size can be controlled separately.
- `maxTypeDescSize`: Limits the size of the data retained for memory used internally for type descriptions. This is to avoid deserializing a stream that defines a large data structure with a large number of members to exhaust available memory.

It is also possible to set all of these variables at the same time by setting `maxSize`. After creating an `ObjIStream`, all of them are set to the maximum `Nat` value, which corresponds to just below 4 GiB.

## Making Classes Serializable

Usually, you don't want to serialize a large amount of primitive types. Usually, it makes more sense to create a type that represents the data you want to serialize, and treat it as a whole unit. Of course this is possible in Storm as well, and as long as all data members are serializable, Storm can generate the serialization code for you automatically.

The function `util.serialize.serializalble(Type)` does just that. Call `serializable` and give a type, and the function will examine the type and generate `write` and `read` functions that correspond to the data in the class. The function is designed to be usable with options in Basic Storm. As such, in Basic Storm (and other languages with similar mechanisms), one can easily make a class serializable as follows:

    use lang:bs:macro;  // for named{}
    use util:serialize; // for serializable

    class MyClass : serializable {
        Int a;
        Str b;
        OtherClass c;
    }

## The Serialization Interface

There might be times when you need to serialize types that consist of non-serializable types, or when the standard representation is not suitable for some reason. This is also possible to solve, but requires more knowledge on the interfaces used by the serialization.

The serialization interface assumes that all serializable types (except for primitive types) contain four functions (two of which are `read` and `write` discussed earlier) as follows:

- `void write(T this, ObjOStream to)`: Write the object to the stream, as mentioned earlier.
- `T read(ObjIStream from)` (static): Read an object from the stream, as mentioned earlier.
- `SerializedType serializedType()` (static): Generate a type description of this type for the serialization. This function is called when it is needed by the serialization system. You typically don't need to call it directly.
- `void __init(T this, ObjIStream from)` (constructor): Initialize the object from a stream, used while reading.

All of these functions are generated automatically when using the `serializable` function mentioned earlier. However, all types implementing `write`, `read` and `serializedType` are considered serializable, and as such it is possible to make all types serializable by implementing these functions manually. Therefore, we will take a closer look at each function below:

### The write function

The write function is responsible for writing the entire object to the stream. This function is responsible for informing the `ObjOStream` of the extents of the object by calling the `startX` and `end` functions as appropriate.

The write function is expected to start by calling `startValue` if the type is a value or `startClass` if the type is a class type. `startValue` takes one parameter, a `Type` value that corresponds to the type to be serialized (it is expected to have a `serializedType` member as above). `startValue` then returns a boolean indicating whether or not this instance needs serialization. If serialization is required, the read function is expected to serialize any data contained in the object, and then call the `end` function of the `ObjOStream` class to indicate that serialization is complete. If the class has a superclass, the superclass is expected to be serialized before any data is serialized by calling the superclass' `write` function. Serialization of the contained data depends on the contents of the `SerializedType` provided, as will be explained later.

If the serialized object is a class type, the `write` function is expected to call `startClass` instead of `startValue`. `startClass` works very much like `startValue`, but in order to properly handle shared instances and cyclic hierarchies, `startClass` takes an additional parameter: a reference to the instance being serialized. Except for this difference, serialization of class types work the same as for value types. As such, a `write` function usually looks like this:

    use lang:bs:macro;  // for named{}

    class MyClass {
        Int a;
        Str b;

        // other members...

        void write(ObjOStream to) {
            if (to.startClass(named{MyClass}, this)) {
                // call super:write(to) if it exists
                a.write(to);
                b.write(to);
                to.end();
            }
        }
    }

Note that `end` is not called if `startClass` or `startValue` returns `false`.

The write function is the only function (except for `serializedType`, if used) that is required for serialization of objects.

### The read function

Since the built-in serialization attempts to be robust, deserialization is a bit more complicated than serialization. Deserialization starts in the `read` function. However, the `read` function is usually very simple, and can be implemented automatically by the `serializable` function provided the other three functions are present. The `write` functions job is simply to call `readValue` or `readClass` depending on what is being read from the stream.

The `readValue` function takes two parameters: a `Type` instance corresponding to the type being deserialized and a raw pointer to previously allocated memory big enough to hold an instance of the deserialized type. Because of this generic, low-level API, it is not generally possible to call this function from higher level languages, such as Basic Storm, since they do not allow access to raw memory in this fashion. However, if the other three functions are present, the `serializable` function will happily generate a suitable `read` function for any type.

The `readClass` function is a bit better in this regard, as it only takes a `Type` parameter indicating the type to deserialize. The resulting instance is returned in the form of an `Object` reference. As such, the `read` function for class types can be implemented as follows (note that the implementation provided by the `serializable` function is slightly more efficient, as it skips the type check required in Basic Storm):

    use lang:bs:macro;  // for named{}

    class MyClass {
        // ...
        MyClass read(ObjIStream from) : static {
            if (x = from.readClass(named{MyClass})) {
                x;
            } else {
               // Or something else...
               MyClass();
            }
        }
    }

Both `readClass` and `readValue` will locate the `serializedType` member of the type that is to be deserialized (in the case of `readClass`, it may be a subclass), call that function, and call the deserialization constructor contained in the resulting `SerializedType` class to create an instance of the deserialized type. Do not assume that objects are created in the same order that the corresponding 'read' functions are called; the `ObjIStream` may need to cache objects when the order of members in the stream don't match the order of members in the current code, and it may even re-use previously created instances in case multiple references refer to the same object.

### The serializedType function

The `serializedType` function is sometimes called by the serialization mechanism during deserialization to retrieve type information about arbitrary types in the system. The task of this function is therefore simple: return an instance of `SerializedType` that describes the aspects of the type relevant for serialization.

There are three subclasses of `SerializedType` that are recognized by the serialization system: `SeralizedStdType`, `SerializedTuples` and `SerializedMaybe`. All of these subclasses, or the base class, may be returned from the `serializedType` function. Each subclass represent a standard serialization format that can be interpreted without having access to the implementation of the class itself. The base class, however, represents an object that is serialized in a nonstandard way and thus not possible to deserialize without access to the actual implementation.

- `SerializedType`: Contains the minimum amount of information required by the serialization system to function: A reference to the `Type` and a pointer to the read constructor used for deserialization. If a `serializedType` returns an instance of this type, it indicates that the serialized representation of the type is custom. Since the high-level serialization relies on type information that is not available during custom serialization, custom serialization functions may not call any serialization functions for other objects. Custom serialization must instead call low-level operations on the underlying stream (available as a member variable of the `ObjOStream` and `ObjIStream`). It is still possible to serialize primitive types in this manner, as they provide `read` and `write` functions for regular `OStream` and `IStream` objects.
- `SerializedStdType`: Indicates that the type is a standard type, consisting of a number of named member variables. Thus, this class contains the name of each member variable and its type. The type is expected to serialize its members in the order specified in this type by calling the corresponding `write` and `read` functions. This is the type generated by `serializable` if nothing else is specified.
- `SerializedTuples`: Indicates that the type consists of a number of tuples, each tuple consisting of a fixed number of elements with pre-defined types. Thus, this class contains information on which types are stored in each tuple. The type is expected to serialize its data by first writing a `Nat` containing the number of tuples stored, followed by that many tuples stored by calling the `write` or `read` function of each type in the tuple the specified number of times. This type is used by the standard containers, such as `Array` and `Map`.
- `SerializedMaybe`: Indicates that the type may contain a value of some type. The class contains information about a single type that may be stored in the stream. The type is expected to be serialized by writing a single `Bool` that indicates if the type is present or not. If so, the bool is followed by the type itself. This type is used by the `Maybe` type.

In most languages, it is necessary to implement this function by creating the desired instance each time the function is called, which is slightly inefficient (the object streams make sure to only call the function once for each instance). The `serialized` function generates a version that always returns the same instance, which makes this implementation slightly more efficient. If a `write` function and a read constructor is present, the `serialized` function will generate a `serializedType` function that returns an instance of `SerializedType` to indicate that the type has custom serialization.

### The read constructor

The read constructor takes a single `ObjIStream` as a parameter, and is responsible for populating an object from a stream, the opposite of the `write` function. As previously mentioned, the read constructor is called as needed by the serialization system, and it is located using the `serializedType` function.

The read constructor is usually a bit simpler than the `write` function. Since the `read` function is responsible for calling `readX` (corresponding to `startX` for `write`), the read constructor does not need to do that. The read constructor can simply assume that an instance of the current type is to be read from the stream immediately. For both value and class types, the constructor is assumed to read the data for the class in the same way data is written (except, of course, calling `read` instead of `write`), and using the retrieved data to initialize the object. If the object has a superclass, the superclass is expected to be read before any members of the subclass. As such, read constructors are typically implemented as follows in Basic Storm:

    class MyClass {
        Int a;
        Str b;

        // other members...

        init(ObjIStream from) {
            // call super(from) if it exists
            init {
                a = Int:read(from);
                b = Int:read(from);
            }
            from.end();
        }
    }

If you implement custom serialization and deserialize an array, you are additionally expected to call the `checkArrayAlloc` function before allocating the array. This allows the `ObjIStream` to keep track of size limits properly. If the limits are exceeded, the `checkArrayAlloc` function throws an appropriate error.

### Examples

To summarize custom serialization, we provide two examples in Basic Storm. The first example shows how one can implement the standard serialization using the custom serialization mechanisms described above. Here, we have class with two members, `a` and `b` that are serialized:

    use lang:bs:macro;  // for named{}
    use util:serialize; // for serializable

    class MyClass : serializable /* provides "read", which is difficult to implement in Basic Storm */ {
        Int a;
        Str b;

        SerializedType serializedType() : static {
            SerializedStdType t(named{MyClass}, &__init(MyClass, ObjIStream));
            t.add("a", named{Int});
            t.add("b", named{Str});
            t;
        }

        void write(ObjOStream to) {
            if (to.startClass(serializedType(), this)) {
                a.write(to);
                b.write(to);
                to.end();
            }
        }

        init(ObjIStream from) {
            init {
                a = Int:read(from);
                b = Str:read(from);
            }
            from.end();
        }
    }

The next example shows how to implement custom serialization of the same class. This version will produce a serialized stream that does not contain enough information to read without the implementation of `MyClass`. Note, that this is unnecessary, as `MyClass` is simple enough to be serializable using the standard mechanisms:

    use lang:bs:macro;  // for named{}
    use util:serialize; // for serializable

    class MyClass : serializable /* provides "read" and "serializedType" */ {
        Int a;
        Str b;

        void write(ObjOStream to) {
            // Note: 'serializedType' is generated by the 'serializable' decorator.
            if (to.startClass(serializdType(), this)) {
                // Note: We're writing to the underlying 'OStream' rather than the 'ObjOStream'.
                OStream stream = to.to;
                a.write(stream);
                b.write(stream);
                to.end();
            }
        }

        init(ObjIStream from) {
            // Note: We're reading from the underlying 'IStream' rather than the 'ObjIStream'.
            IStream stream = from.from;
            init {
                a = Int:read(stream);
                b = Str:read(stream);
            }
            from.end();
        }
    }

## Robustness of the format

When storing data for longer periods of time, it is important to know what changes can be made to the serialized classes without losing the ability to read previously stored data. For this reason, this section describes what changes are handled by the serialization mechanism, and which are not.

All of the following scenarios assume that some data structure is serialized to an external location. The data representation is then changed as indicated before the previously stored data is deserialized again. Furthermore, we assume that the implementation of any classes that have custom serialization remain unchanged, as the serialization system can make no guarantees about them. The serialized data may, however, include types that use custom serialization as long as that implementation remain compatible with the previous version.

### Member variables

Member variables are usually stored in the serialized stream in the order they were declared in the class (or, more precisely, the order in which they are stored in memory, which currently correspond to the order the member variables are declared, but may change in the future). Thus, it is reasonable to question if re-ordering the member variables break compatibility.

Indeed, changing the order of member variables will change the order in which they are written to the stream. However, the serialization system will detect this using the names of the members and act as if the members appear in the order the deserializing program expects them to. This does require additional bookkeeping and causes a small performance impact on deserialization. Since names are important, care must be taken when re-naming members in serializable classes to not break compatibility. Compatibility can be retained by implementing custom serialization as shown above, and using the old name for the variable in the `SerializedStdType` instance returned from `serializedType`. In the future, it will be possible to specify this without implementing serialization manually.

Adding and removing member variables is supported. If a stream contains a member variable that does not exist in the current version, the variable is simply ignored. On the other hand, if a member variable is missing from the stream, the system will either use the initializer specified in the class (e.g. `Int x = 10;`), or the default constructor of the type. If neither initializer nor a default constructor exists, deserialization will fail with an exception. As such, the system supports removing member variables, and adding member variables as long as a default value is specified.

### Types

It is possible to deserialize a stream into an object representation as long as all types used in the stream are still present in the program. All types need to have the same name and reside in the same package as the time they were serialized. If inheritance is used, the superclasses of all serialized objects also need to remain the same. It is, however, possible to deserialize a value type into a class type (assuming the contents match), but not vice versa.

The serialization system only examines the types that actually appear in a stream. This means that types that could appear in a stream, but do not actually appear in the stream, may be changed in any way without impacting the ability to deserialize streams. This means that it is possible to add new subclasses to previously serialized classes without impairing the ability to deserialize old streams, and newly serialized streams will be readable by old implementations as long as the new classes are not actually serialized.

## The binary format

Primitive types are encoded as follows:

- `Bool`: A single byte containing the value 0 for `false`. Other values (usually 1) indicate `true`.
- `Byte`: A single byte containing the value.
- `Int` and `Nat`: 4 bytes stored in big endian.
- `Long` and `Word`: 8 bytes stored in big endian.
- `Float`: The IEEE754 representation stored as a `Nat`.
- `Double`: The IEEE754 representation stored as a `Word`.
- `Str`: A `Nat` containing the length of the string followed by that many bytes of data, encoded in UTF-8.

Each serialized type is assigned a type id in the form of a `Nat` in order to refer to types in an efficient manner. Type identifiers may appear before they are defined in the stream, for example when referring to member types in a class. However, types are always defined whenever the first instance of that particular type is read or written to the stream, so that the reader knows the format of the serialized objects without access to the actual implementation. Primitive types have pre-defined ids, defined by the enum `core.io.StoredId`. The id `core.io.StoredId.endId` (value 0) is of special interest. It is used to indicate the absence of a type, or the end of a list. Identifiers below `core.io.StoredId.firstCustomId` are reserved for future primitive types, and shall not be used.

A type description is stored as follows:

- A `Byte` indicating what kind of type is stored, described by the `core.io.TypeInfo` enum.
- A `Str` containing the name of the type, serialized as follows: The format is similar to the textual representation of `core.lang.Name`. Each part is stored as a string ending with the ASCII character 1. If parameters are present, they are surrounded by the ASCII character 2 and 3 (for start- and end parentheses). Each parameter ends in either the ASCII character 4 if passed by value or 5 if passed by reference.
- A `Nat` containing the id of the parent type, or `core.io.StoredId.endId` if none.

The remainder of the type description depends on which values are present in the first byte (`core.io.TypeInfo` is a bitmask):

- `tuple`: This is an array of tuples. A series of `Nat` values containing the id of each type in the tuple are stored. The end of the list is indicated by `core.io.StoredId.endId`.
- `maybe`: This is a maybe-type. A single `Nat` containing the contained type id is stored.
- `custom`: This is a type with custom serialization. Noting more is stored, but the serialized data for instances of this type can not be extracted without asking the actual type.
- Otherwise: This is a regular class- or value type (depending on the presence of `classType`). The members of the class or value are stored as pairs, first a `Nat` containing the type id of the member, followed by a `Str` containing the member's name. The end of the list is indicated by a type with id `core.io.StoredId.endId`. No string follows the last type id.

Each object in a session starts with a single `Nat` describing the type of the stored object, followed by the object itself (which, for the first object in a session, starts with a type definition unless a primitive type was stored). Types are then stored depending on their type (described by the type description) as follows:

If the stored value is a class type (i.e. `core.io.TypeInfo.classType` is not present in the first byte of the type description), a `Nat` describing the object's id is stored first. If this id was serialized before, nothing more is stored; this is a reference to the previously stored object. Otherwise, a `Nat` containing the type of the object follows (as the dynamic type might differ from the static type). If this is a new type id, the type description directly follows the new id, as we need to access that. If any parent classes are not yet serialized, they also follow. Finally, the data of the object is stored as described below.

Values are simpler, as their static type always matches their dynamic type, and they are always copied. Therefore, values are always stored as the data of the object, without the additional headers required for storing class types.

The data of the object also depends on the first byte of the type description as follows:

- If the type is a built-in type, use the default serialization of that type (the `read` and `write` functions for a regular `IStream` and `OStream`).
- If the type is an array of tuples, a `Nat` containing the number of elements in the array, followed by that many repetitions of the types in the type description.
- If the type is a maybe type, a `Bool` indicating the presence of a value is stored first. If the value is true, then a single instance of the contained type is stored directly afterwards.
- If the type is a custom type, the representation is unspecified.
- If the type is a regular class- or value type, each member is serialized in the order specified by the type description.

## Example

Consider the code below, found in the file `serialization.bs` in the `demo` package:

    use core:io;
    use util:serialize;

    value Val : serializable {
        Int a;
        Str b;

        init(Int a, Str b) {
            init() { a = a; b = b; }
        }
    }

    class Base : serializable {
        Int a;

        init(Int a) {
            init() { a = a; }
        }
    }

    class Derived : extends Base, serializable {
        Int b;

        init(Int a, Int b) {
            init(a) { b = b; }
        }
    }

    class Wrap : serializable {
        Val a;
        Val b;
        Base c;
        Base d;
        Base e;

        init() {
            Base shared(5);
            init() {
                a = Val(1, "One");
                b = Val(2, "Two");
                c = Derived(3, 4);
                d = shared;
                e = shared;
            }
        }
    }

    void serializeExample() {
        MemOStream buffer;
        ObjOStream out(buffer);

        // First session, an entire Wrap object.
        Wrap().write(out);

        // Second session, an array of Val objects.
        [Val(10, "Ten"), Val(20, "Twenty")].write(out);

        // Print a textual version to the screen.
        TextObjStream text(MemIStream(buffer.buffer));
        print("Session 1:");
        print(text.read());
        print("Session 2:");
        print(text.read());

        // Print the data to the screen as well.
        print(buffer.buffer.toS);
    }

The function `serializeExample` serialize an instance of the `Wrap` class and as the first session, followed by an array of `Val` objects as the second session. Then, the textual representation is printed followed by the binary representation. The textual representations illustrate how the data is structured by the serialization mechanism, and should be fairly apparent from the output:

Session 1 (the `Wrap` object):

    demo.Wrap (instance 0) {
        a: demo.Val {
            a: 1i
            b: "One"
        }
        b: demo.Val {
            a: 2i
            b: "Two"
        }
        c: demo.Derived (instance 1) {
            a: 3i
            b: 4i
        }
        d: demo.Base (instance 2) {
            a: 5i
        }
        e: <link to instance 2>
    }

Session 2 (the array of `Val` objects):

    core.Array(demo.Val) (instance 0) [
        demo.Val {
            a: 10i
            b: "Ten"
        }
        demo.Val {
            a: 20i
            b: "Twenty"
        }
    ]

The data is represented in the stream as follows. The data is annotated to show how each part is represented by the format.

    00 00 00 20    // Type-id of the root object of this session (32, user defined)
                   // This is a type we don't know about, so we read a type description:
    01             // -Type flags (class type)
                   // -Next is a string containing the mangled name of the class:
    00 00 00 0A    // --Length of the string (10 bytes)
    64 65 6D 6F    // --"demo" in UTF-8
    01             // --End of this part.
    57 72 61 70    // --"Wrap" in UTF-8
    01             // --End of this part.
    00 00 00 00    // -Parent type (none)
                   // -This is a compound type, so we read its members.
    00 00 00 21    // -Type-id of the first member (33, user defined)
    00 00 00 01 61 // -Name of the member (string, "a")
    00 00 00 21    // -Type-id of the second member (33, user defined)
    00 00 00 01 62 // -Name of the member (string, "b")
    00 00 00 22    // -Type-id of the third member (34, user defined)
    00 00 00 01 63 // -Name of the member (string, "c")
    00 00 00 22    // -Type-id of the fourth member (34, user defined)
    00 00 00 01 64 // -Name of the member (string, "d")
    00 00 00 22    // -Type-id of the fifth member (43, user defined)
    00 00 00 01 65 // -Name of the member (string, "e")
    00 00 00 00    // -No more members (type-id 0 is not used)
    00 00 00 00    // Instance of this class (0, not present earlier)
                   // Since instance 0 was not serialized earlier, we read the data now
    00 00 00 20    // Actual type of the instance (32, Wrap)
                   // Start reading an instance of type 33 for "a".
                   // -Type 33 is not known yet, read the type description first.
    00             // --Type flags (value type)
    00 00 00 09    // ---Length of the type name (9 bytes)
    64 65 6D 6F 01 // ---The first part ("demo")
    56 61 6C 01    // ---The second part ("Val")
    00 00 00 00    // --Parent type (none)
    00 00 00 03    // --Type-id of the first member (3, Int)
    00 00 00 01 61 // --Name of the member (string, "a")
    00 00 00 09    // --Type-id of the second member (9, Str)
    00 00 00 01 62 // --Name of the member (string, "b")
    00 00 00 00    // --No more members
                   // -Now, we start reading the "Val" instance for "a" inside "Wrap"
    00 00 00 01    // -Value for "a" inside the "Val" instance (1)
    00 00 00 03    // -Length of the string for "b" in the "Val" instance (3 bytes)
    4F 6E 65       // --Content ("One")
                   // Done reading "Val", continue reading an instance for "b".
    00 00 00 02    // -Value for "a" inside the "Val" instance (2)
    00 00 00 03    // -Length of the string for "b" in the "Val" instance (3 bytes)
    54 77 6F       // --Content ("Two")
                   // Trying to read an instance of id 34 for "c".
    	       // -Type 34 is not known yet, read the type description first.
    01             // --Type flags (class type)
    00 00 00 0A    // --Length of the type name (10 bytes)
    64 65 6D 6F 01 // ---The first part ("demo")
    42 61 73 65 01 // ---The second part ("Base")
    00 00 00 00    // --Parent type (none)
    00 00 00 03    // --Type-id of the first member (3, Int)
    00 00 00 01 61 // --Name of the member (string, "a")
    00 00 00 00    // --No more members
    00 00 00 01    // -Instance id (1, not present earlier)
    00 00 00 23    // -Actual type (35) differs from formal type, previousl unknown.
                   // -Trying to read an instance of 35, we need a type description.
    01             // --Type flags (class type)
    00 00 00 0D    // --Length of the type name (13 bytes)
    64 65 6D 6F 01 // ---The first part ("demo")
    44 65 72 69    //
    76 65 64 01    // ---The second part ("Derived")
    00 00 00 22    // --Parent type (34, Base)
    00 00 00 03    // --Type of the first member (3, Int)
    00 00 00 01 62 // --Name of the member (string, "b")
    00 00 00 00    // --No more members
    00 00 00 03    // -Data for "a" inside "Base" (3)
    00 00 00 04    // -Data for "b" inside "Derived" (4)
                   // Continue reading an instance of type 34 for "d"
    00 00 00 02    // -Instance id (2, not present earlier)
    00 00 00 22    // -Actual type (34, Base)
    00 00 00 05    // -Value for "a" inside "Base")
                   // Continue reading an instance of type 34 for "e"
    00 00 00 02    // -Instance id (2, previously known, reuse that instance)
                   // Done reading "Wrap". Session 1 completed.

    00 00 00 24    // Start of session 2, type-id of the first type (36, not known)
    03             // -Type flags (class type, tuple)
    00 00 00 17    // -Length of the type name (23 bytes)
    63 6F 72 65 01 // --The first part ("core")
    41 72 72 61 79 // --The second part: "Array"
    02 64 65 6D 6F // --"<demo"
    01 56 61 6C 01 // --".Val."
    04 03 01       // --",>."
    00 00 00 00    // -Parent type (none)
    00 00 00 21    // -First type in each tuple (33, Val)
    00 00 00 00    // -End of the list
    00 00 00 00    // Instance id (0, not present earlier in this session)
    00 00 00 24    // Actual type (36, Array<demo.Val>)
    00 00 00 02    // Number of elements (2)
                   // Reading element 0, type 33
    00 00 00 0A    // -Value for "a" (10)
    00 00 00 03    //
    54 65 6E       // -Value for "b" (string, "Ten")
                   // Reading element 1, type 33
    00 00 00 14    // -Value for "a" (20)
    00 00 00 06 54 //
    77 65 6E 74 79 // -Value for "b" (string, "Twenty")
                   // Done reading "Array<demo.Val>". Session 2 completed.