CME Specific Settings
Session Settings
SetLastMsgSeqNumProcessed Option to set the LastMsgSeqNumProcessed (tag 369) field in each outgoing message. This is the required tag for the CME iLink system, so the option should be
true
.RequestOnlyMissedMessages Option to turn on/off the alternative algorithm of the resend logic, which is compatible with the CME Enhanced Resend Logic. CME highly recommends using this logic, so the option should be
true
.ResendRequestMaximumRange Option to set the maximum number of messages to be requested in one Resend Request message. For all resend requests, CME requires that the client system request the maximum limit of
2500
messages, so the option value should be2500
.ConsiderRejectOnResendRequestAsGapFill By default, the FIX Engine disconnects the session when the Session Level Reject message is received in reply on the "Resend Request" message. However, CME services (e.g. Drop Copy) send the "Session Level Reject" message in some cases, in which the session should not be disconnected. Therefore, in such cases, the option should be
true
.
Migration to MSGW
CME MSGW does not require any new functionalities from the FIX Engine. There is only one main change on the application level.
For MSGW, if you want to trade two different products on different Market Segment Gateways, then you need to create two FIX sessions with the same SenderCompId
, SenderSubId
, and TargetCompId
,
but with different TargetSubId
for the particular Market Segment Gateway.
After that, FIX sessions need to be connected to the corresponding IP address and port of the specific Market Segment Gateway.
Note
To create two FIX sessions with the same SenderCompId
and TargetCompId
, you should use the constructor with the additional customSessionKey
parameter.
Secure Logon
CME Globex implemented secure authentication for iLink and Drop Copy sessions on Convenience Gateway (CGW) and Market Segment Gateway (MSGW).
The new logon procedure secures the client system logon with:
- Customer identity verification - a client system Logon request will be signed with security credentials issued and validated by CME Group.
- Message confidentiality and integrity - to credential the Logon message, the client system sends a keyed-hash message authentication code (
HMAC
) generated from a combination of the Logon message field values.
When CME Globex receives the Logon message, it uses the identical inputs to calculate the HMAC
value to validate
against the Logon request. If the values do not match, CME Globex rejects the logon.
Customers must create secure key pairs for iLink and Drop Copy Sessions in the CME Customer Center.
CME Secure Logon requires to add several specific tags to the Logon message.
Implementing Secure Logon consists of two parts:
- Creating the canonical request string.
- Calculating
HMAC
.
The canonical request string is just a union of several fields from Logon message. Please note, that Session fills values of some fields like SendingTime
right before the message is sent,
so its values should be taken from the OutboundSessionMessage event.
To calculate HMAC
, standard .NET classes from the System.Security.Cryptography
namespace are used.
The detailed description of the CME Secure Logon procedure can be found at CME Globex API Secure Logon.
The sample below demonstrates the basics of implementing CME Secure Logon.
#region Copyright
/*
* Copyright Onix Solutions Limited [OnixS]. All rights reserved.
*
* This software owned by Onix Solutions Limited [OnixS] and is protected by copyright law
* and international copyright treaties.
*
* Access to and use of the software is governed by the terms of the applicable OnixS Software
* Services Agreement (the Agreement) and Customer end user license agreements granting
* a non-assignable, non-transferable and non-exclusive license to use the software
* for it's own data processing purposes under the terms defined in the Agreement.
*
* Except as otherwise granted within the terms of the Agreement, copying or reproduction of any part
* of this source code or associated reference material to any other location for further reproduction
* or redistribution, and any amendments to this copyright notice, are expressly prohibited.
*
* Any reproduction or redistribution for sale or hiring of the Software not in accordance with
* the terms of the Agreement is a violation of copyright law.
*/
#endregion
using OnixS.Fix;
using OnixS.Fix.Fix42;
using System;
using System.Security.Cryptography;
using System.Text;
namespace Snippets
{
internal static class CmeTags
{
public const int EncryptedPasswordMethod = 1400;
public const int EncryptedPasswordLength = 1401;
public const int EncryptedPassword = 1402;
/// <summary>
/// Identifies system generating the message.
/// </summary>
public const int ApplicationSystemName = 1603;
/// <summary>
/// Identifies the version of the system generating the message.
/// </summary>
public const int TradingSystemVersion = 1604;
/// <summary>
/// Identifies the vendor of the application system.
/// </summary>
public const int ApplicationSystemVendor = 1605;
}
internal class CmeSpecificSettings
{
public CmeSpecificSettings(Session session)
{
session.OutboundSessionMessage += OnOutboundSessionMessage;
}
private void OnOutboundSessionMessage(object sender, MessageEventArgs args)
{
if (args.Message.Type == MsgType.Logon && !string.IsNullOrEmpty(AccessKeyId) && !string.IsNullOrEmpty(SecretKey))
{
args.Message.Set(Tag.EncodedTextLen, AccessKeyId.Length)
.Set(Tag.EncodedText, SecretKey)
.Set(CmeTags.EncryptedPasswordMethod, "CME-1-SHA-256")
.Remove(Tag.EncryptMethod);
string canonicalRequest = CreateCanonicalRequest(args.Message, (Session)sender);
string hash = CalculateHmac(canonicalRequest, SecretKey);
args.Message.Set(CmeTags.EncryptedPasswordLength, hash.Length)
.Set(CmeTags.EncryptedPassword, hash);
}
}
/// <summary>
/// Security credential from CME to sign Logon request to iLink or Drop Copy.
/// </summary>
public string AccessKeyId { get; set; }
/// <summary>
/// Security credential from CME to create HMAC signature.
/// </summary>
public string SecretKey { get; set; }
private string CreateCanonicalRequest(Message customLogon, Session session)
{
StringBuilder sb = new StringBuilder();
const char delimiter = '\n';
sb.Append(session.OutSeqNum).Append(delimiter)
.Append(customLogon.SenderCompId).Append(delimiter)
.Append(customLogon[Tag.SenderSubID]).Append(delimiter)
.Append(customLogon[Tag.SendingTime]).Append(delimiter)
.Append(customLogon[Tag.TargetSubID]).Append(delimiter)
.Append(session.HeartBtInt).Append(delimiter)
.Append(customLogon[Tag.SenderLocationID]).Append(delimiter)
.Append(customLogon[Tag.LastMsgSeqNumProcessed]).Append(delimiter)
.Append(customLogon[CmeTags.ApplicationSystemName]).Append(delimiter)
.Append(customLogon[CmeTags.TradingSystemVersion]).Append(delimiter)
.Append(customLogon[CmeTags.ApplicationSystemVendor]);
return sb.ToString();
}
private string CalculateHmac(string canonicalRequest, string userKey)
{
byte[] bytes = Base64UrlDecode(userKey);
var hmac = new HMACSHA256(bytes);
byte[] requestBytes = Encoding.UTF8.GetBytes(canonicalRequest);
byte[] hash = hmac.ComputeHash(requestBytes);
return Base64UrlEncode(hash);
}
private static byte[] Base64UrlDecode(string arg)
{
string s = arg;
s = s.Replace('-', '+'); // 62nd char of encoding
s = s.Replace('_', '/'); // 63rd char of encoding
switch (s.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 2: s += "=="; break; // Two pad chars
case 3: s += "="; break; // One pad char
default:
throw new FormatException("Illegal base64url string!");
}
return Convert.FromBase64String(s); // Standard base64 decoder
}
private static string Base64UrlEncode(byte[] arg)
{
string s = Convert.ToBase64String(arg); // Regular base64 encoder
s = s.Split('=')[0]; // Remove any trailing '='s
s = s.Replace('+', '-'); // 62nd char of encoding
s = s.Replace('/', '_'); // 63rd char of encoding
return s;
}
}
}