real-logic / simple-binary-encoding

Simple Binary Encoding (SBE) - High Performance Message Codec
Apache License 2.0
3.13k stars 523 forks source link

OftDecoder doesn't provide the name of the type on the typeToken when encoding is nested in a composite #477

Closed mikeb01 closed 7 years ago

mikeb01 commented 7 years ago

If I have a type nested within a composite, when I arrive at the onEncoding callback the typeToken doesn't reference the type as described in the type parameter of the ref element: <ref name="otherInstructionId" type="Fix24CharString"/>.

Using the supplied code I get the following output. When we arrive at the instructionId field on the message the typeToken contains the name of the correct type (Fix24CharString), where as we we arrive at orderState.otherInstructionId, the typeToken contains the name of the ref (otherInstructionId), where as I would expect either the name or referencedName to contain the name of the type:

instructionId
fieldToken: Token{signal=BEGIN_FIELD, name='instructionId', referencedName='null', description='null', id=4, version=0, deprecated=0, encodedLength=0, offset=0, componentTokenCount=3, encoding=Encoding{presence=REQUIRED, primitiveType=null, byteOrder=LITTLE_ENDIAN, minValue=null, maxValue=null, nullValue=null, constValue=null, characterEncoding='null', epoch='unix', timeUnit=nanosecond, semanticType='null'}}
typeToken : Token{signal=ENCODING, name='Fix24CharString', referencedName='null', description='null', id=-1, version=0, deprecated=0, encodedLength=24, offset=0, componentTokenCount=1, encoding=Encoding{presence=REQUIRED, primitiveType=CHAR, byteOrder=LITTLE_ENDIAN, minValue=null, maxValue=null, nullValue=null, constValue=null, characterEncoding='US-ASCII', epoch='unix', timeUnit=nanosecond, semanticType='null'}}
orderState.otherInstructionId
fieldToken: Token{signal=ENCODING, name='otherInstructionId', referencedName='null', description='null', id=-1, version=0, deprecated=0, encodedLength=24, offset=0, componentTokenCount=1, encoding=Encoding{presence=REQUIRED, primitiveType=CHAR, byteOrder=LITTLE_ENDIAN, minValue=null, maxValue=null, nullValue=null, constValue=null, characterEncoding='US-ASCII', epoch='null', timeUnit=null, semanticType='null'}}
typeToken : Token{signal=ENCODING, name='otherInstructionId', referencedName='null', description='null', id=-1, version=0, deprecated=0, encodedLength=24, offset=0, componentTokenCount=1, encoding=Encoding{presence=REQUIRED, primitiveType=CHAR, byteOrder=LITTLE_ENDIAN, minValue=null, maxValue=null, nullValue=null, constValue=null, characterEncoding='US-ASCII', epoch='null', timeUnit=null, semanticType='null'}}

Schema:

<?xml version="1.0" encoding="UTF-8"?>
<sbe:messageSchema
        xmlns:sbe="http://fixprotocol.io/2016/sbe"
        package="sbeir.bug"
        id="10001"
        version="209"
        description="Order Book Events persistent structure"
        byteOrder="littleEndian">

    <types>
        <composite name="messageHeader" description="Message identifiers and length of message root">
            <type name="blockLength" primitiveType="uint16"/>
            <type name="templateId" primitiveType="uint16"/>
            <type name="schemaId" primitiveType="uint16"/>
            <type name="version" primitiveType="uint16"/>
        </composite>
        <type name="Fix24CharString" primitiveType="char" length="24"/>
        <composite name="EvOrderState">
            <ref name="otherInstructionId" type="Fix24CharString"/>
        </composite>
    </types>

    <sbe:message name="ExecutionReport" id="3">
        <field name="instructionId" id="4" type="Fix24CharString"/>
        <field name="orderState" id="5" type="EvOrderState"/>
    </sbe:message>

</sbe:messageSchema>

Code:

import org.agrona.DirectBuffer;
import org.agrona.concurrent.UnsafeBuffer;
import org.junit.Test;
import sbeir.bug.ExecutionReportEncoder;
import sbeir.bug.MessageHeaderEncoder;
import uk.co.real_logic.sbe.ir.Ir;
import uk.co.real_logic.sbe.ir.IrDecoder;
import uk.co.real_logic.sbe.ir.Token;
import uk.co.real_logic.sbe.otf.AbstractTokenListener;
import uk.co.real_logic.sbe.otf.OtfHeaderDecoder;
import uk.co.real_logic.sbe.otf.OtfMessageDecoder;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;

import static java.util.stream.Collectors.joining;

public class Bug
{
    @Test
    public void test() throws IOException
    {
        UnsafeBuffer buffer = new UnsafeBuffer(new byte[4096]);
        MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();
        ExecutionReportEncoder executionReportEncoder = new ExecutionReportEncoder();

        executionReportEncoder.wrapAndApplyHeader(buffer, 0, headerEncoder);
        executionReportEncoder.instructionId("abcdef");
        executionReportEncoder.orderState().otherInstructionId("ghijk");

        final ByteBuffer byteBuffer = readOrderBookEventsIr("/bug.sbeir");

        Ir orderBookEventsIr = new IrDecoder(byteBuffer).decode();
        OtfHeaderDecoder headerDecoder = new OtfHeaderDecoder(orderBookEventsIr.headerStructure());

        final int blockLength = headerDecoder.getBlockLength(buffer, 0);
        final int templateId = headerDecoder.getTemplateId(buffer, 0);

        OtfMessageDecoder.decode(
            buffer,
            headerDecoder.encodedLength(),
            209,
            blockLength,
            orderBookEventsIr.getMessage(templateId),
            new AbstractTokenListener()
            {
                private final Deque<String> path = new ArrayDeque<>();
                private int compositeDepth = 0;

                @Override
                public void onBeginComposite(final Token fieldToken, final List<Token> tokens, final int fromIndex, final int toIndex)
                {
                    if (compositeDepth > 0)
                    {
                        path.addLast(tokens.get(fromIndex).name());
                    }
                    else
                    {
                        path.addLast(fieldToken.name());
                    }
                    compositeDepth++;
                }

                @Override
                public void onEndComposite(final Token fieldToken, final List<Token> tokens, final int fromIndex, final int toIndex)
                {
                    path.removeLast();
                    compositeDepth--;
                }

                @Override
                public void onEncoding(final Token fieldToken, final DirectBuffer buffer, final int bufferIndex, final Token typeToken, final int actingVersion)
                {
                    path.addLast(fieldToken.name());
                    System.out.printf("%s%n", path.stream().collect(joining(".")));
                    System.out.printf("fieldToken: %s%n", fieldToken);
                    System.out.printf("typeToken : %s%n", typeToken);
                    path.removeLast();
                }
            });
    }

    private ByteBuffer readOrderBookEventsIr(String s) throws IOException
    {
        FileChannel open = FileChannel.open(new File("bug.sbeir").toPath(), StandardOpenOption.READ);
        return open.map(FileChannel.MapMode.READ_ONLY, 0, open.size());
    }
}
mjpt777 commented 7 years ago

I missed the case on a simple type. Does this work now for you?

mikeb01 commented 7 years ago

This works. Another observation. The fieldToken supplied when in the onEncoding callback is not a token representing the field, but matches the typeToken. To be consistent with the java doc, I would of expected it to be the token for the message field, so should have the name orderState instead of originalInstructionId which is the name of the ref within the composite.

mjpt777 commented 7 years ago

This is the fair point. With the extension of composites from RC2 this has evolved and show be the field or containing containing composite.

mjpt777 commented 7 years ago

Note that composites can be nested to any depth.

mikeb01 commented 7 years ago

May as well close this now, I have some more questions on the OtfDecoder, but I can follow up in gitter.