IMessage interface
IMessage interface represents a generic model of a SBE message; to manipulate such a message, you need to know the message Template ID and its structure.
Tag-based API
Message fields are accessed via the standard tag-based API. The id
attributes in the message schema describe tag numbers.
For example:
<ns2:message name="Negotiate500" id="500" description="Negotiate" blockLength="76" semanticType="Negotiate">
<field name="CustomerFlow" id="39000" type="ClientFlowType" description="Constant value representing type of flow from customer to CME" semanticType="String"/>
<field name="HMACVersion" id="39003" type="HMACVersion" description="Constant value representing CME HMAC version" semanticType="String"/>
<field name="HMACSignature" id="39005" type="String32Req" description="Contains the HMAC signature." offset="0" semanticType="String"/>
<field name="AccessKeyID" id="39004" type="String20Req" description="Contains the AccessKeyID assigned to this session on this port." offset="32" semanticType="String"/>
<field name="UUID" id="39001" type="uInt64" description="Session Identifier defined as type long (uInt64); recommended to use timestamp as number of microseconds since epoch (Jan 1, 1970)" offset="52" semanticType="int"/>
<field name="RequestTimestamp" id="39002" type="uInt64" description="Time of request; recommended to use timestamp as number of nanoseconds since epoch (Jan 1, 1970)" offset="60" semanticType="int"/>
<field name="Session" id="39006" type="String3Req" description="Session ID" offset="68" semanticType="String"/>
<field name="Firm" id="39007" type="String5Req" description="Firm ID" offset="71" semanticType="String"/>
<data name="Credentials" id="39008" type="DATA" description="Not used and will be set to 0" semanticType="data"/>
</ns2:message>
Named constants
The "Code Generator" tool can generate named constants for all field tags.
For example:
dotnet OnixS.SimpleBinaryEncoding.CodeGenerator.dll ilinkbinary.xml CmeILinkHeader Trading --source true
public static class Tag
{
public const int AccessKeyID = 39004;
public const int AffectedOrderID = 535;
public const int AggressorIndicator = 1057;
...
public const int CustomerFlow = 39000;
...
public const int HMACVersion = 39003;
...
Template Library
An SBE Schema represents an XML-based description of encoding and decoding rules per the SBE specification. The SBE Codec using this information to decode or encode SBE messages.
To use an SBE encoder or decoder, first, create the TemplateLibrary instance using the SBE Schema file content:
var lib = TemplateLibrary.Parse(File.ReadAllText(@"../../../../templates/SbeMessageTemplates.xml"));
Generating Encoder and Decoder
The encoder and decoder can be generated after constructing the Template Library:
var generator = new Generator();
var assembly = generator.GenerateAssembly<CmeILinkHeader>(lib, "OnixS.SimpleBinaryEncoding.Sample");
var decoder = (IDecoder)Activator.CreateInstance(assembly.GetType("OnixS.SimpleBinaryEncoding.Sample.Decoder", true));
var encoder = (IEncoder)Activator.CreateInstance(assembly.GetType("OnixS.SimpleBinaryEncoding.Sample.Encoder", true));
Decoding
The IDecoder object creates an SBE message from the given byte chunk.
IMessage Get<FIELD-TYPE>
methods can be used to read field values:
- Get(int)
- GetBoolean(int)
- GetByte(int)
- GetBytes(int)
- GetChar(int)
- GetComposite(int)
- GetComposite<T>(int)
- GetDecimal(int)
- GetEnum<T>(int)
- GetGroup(int)
- GetInteger(int)
- GetLong(int)
- GetMaturityMonthYear(int)
- GetShort(int)
- GetSignedByte(int)
- GetString(int)
- GetTimestamp(int)
- GetUnsignedInteger(int)
- GetUnsignedLong(int)
- GetUnsignedShort(int)
- GetVariableLengthField(int)
For example:
var negotiate = decoder.Wrap(negotiateMemorySegment);
Console.WriteLine(@$"
hmacSignature={negotiate.GetString(Tag.HMACSignature)},
accessKeyID={negotiate.GetString(Tag.AccessKeyID)},
UUID={negotiate.GetUnsignedLong(Tag.UUID)},
requestTimestamp={negotiate.GetUnsignedLong(Tag.RequestTimestamp)},
session={negotiate.GetString(Tag.Session)},
firm={negotiate.GetString(Tag.Firm)}
");
Reading optional fields
To read optional message fields, use the corresponding TryGet<FIELD-TYPE>
method:
- TryGetByte(int)
- TryGetChar(int)
- TryGetDecimal(int)
- TryGetInteger(int)
- TryGetLong(int)
- TryGetShort(int)
- TryGetSignedByte(int)
- TryGetUnsignedInteger(int)
- TryGetUnsignedLong(int)
- TryGetUnsignedShort(int)
These methods return nullable values. If the message field is absent, the null
value is returned.
For example:
var quoteCancelAck = decoder.Wrap(quoteCancelAckSegment);
Assert.Null(quoteCancelAck.TryGetChar(Tag.UnsolicitedCancelType));
Assert.Null(quoteCancelAck.TryGetUnsignedShort(Tag.QuoteRejectReason));
Assert.Null(quoteCancelAck.TryGetByte(Tag.TotNoQuoteEntries));
To check the field presence, use the Contains(int) method.
Encoding
The IEncoder object creates a new message that wraps the given buffer. Because an SBE message can have a variable length, a customer code is responsible for calculating the required size and creating a corresponding buffer.
The current message length is accessible via the MessageLength property.
IMessageHeader setters are used to set field values.
For example:
int NegotiateMessageTemplateId = 500;
MemoryPointer buffer = new(new byte[65536]);
var negotiate = encoder.Wrap(NegotiateMessageTemplateId, buffer)
.SetString(Tag.HMACSignature, "HMAC")
.SetString(Tag.AccessKeyID, "AccessKey")
.SetUnsignedInteger(Tag.UUID, 1)
.SetUnsignedLong(Tag.RequestTimestamp, (ulong)(DateTime.UtcNow.Ticks - new DateTime(1970, 1, 1).Ticks) * 1000000 / TimeSpan.TicksPerMillisecond)
.SetString(Tag.Session, "ABC")
.SetString(Tag.Firm, "ABCDE");
IMessage message = (IMessage)negotiate;
Console.WriteLine($"SBE encoded memory region: {Encoding.Default.GetString(message.Buffer[..message.MessageLength])}");
Writing optional fields
The IEncoder object sets optional fields to null
values when it creates a new message.
To explicitly set the field value to the null
value, use the SetToNull(int) method.
Repeating groups
Repeating groups are represented by the IGroup interface.
The repeating group API implements the Iterator pattern to reduce the GC load. There are no objects created for group entries. Group entry fields can be accessed via getters and setters in the same manner as for IMessage.
To advance the repeating group to the next entry, use the MoveNext() method.
Example:
int MassQuoteAck545MessageTemplateID = 545;
MemoryPointer buffer = new(new byte[65536]);
var quoteAck = encoder.Wrap(MassQuoteAck545MessageTemplateID, buffer);
// Create a repeating group with two entries.
IGroup group = quoteAck.SetGroup(Tag.NoQuoteEntries, 2);
group.Reset();
group.MoveNext();
group.SetUnsignedInteger(Tag.QuoteEntryID, 1);
group.SetInteger(Tag.SecurityID, 12212);
group.SetUnsignedShort(Tag.QuoteSetID, 452);
group.MoveNext();
group.SetUnsignedInteger(Tag.QuoteEntryID, 2);
group.SetInteger(Tag.SecurityID, 141234424);
group.SetUnsignedShort(Tag.QuoteSetID, 32339);
// Decode a repeating group.
var quoteAck = decoder.Wrap(quoteAckMemorySegment);
IGroup group = quoteAck.GetGroup(Tag.NoQuoteEntries);
group.Reset();
while (group.MoveNext())
{
Console.WriteLine($@"
QuoteEntryID={group.GetUnsignedInteger(Tag.QuoteEntryID)}
SecurityID={group.GetInteger(Tag.SecurityID)}
QuoteSetID={group.GetUnsignedShort(Tag.QuoteSetID)}
");
}
Note
Each GetGroup(int) and SetGroup(int, int) call returns the same repeating group object.
GC Free interface
Use WrapPreCreatedMessage(int, MemoryPointer) and WrapPreCreatedMessage(MemoryPointer) methods to prevent IMessage instance allocation. For the same message Template ID, these methods return the same IMessage instance stored inside IEncoder or IDecoder instances.
This approach allows to encode/decode SBE messages without memory allocation on the critical path.
Note
These methods should be used carefully because each subsequent call for the same message Template ID returns the same IMessage instance.
OnixS.SimpleBinaryEncoding.IMessage negotiate1 = decoder.WrapPreCreatedMessage(buffer1);
OnixS.SimpleBinaryEncoding.IMessage negotiate2 = decoder.WrapPreCreatedMessage(buffer2);
Assert.True(ReferenceEquals(negotiate1, negotiate2));
In the example above, negotiate1
and negotiate2
are references to the same IMessage instance, which refers
the buffer2
memory segment.