• Version 1.16.0
Show / Hide Table of Contents

SSL SellSide Sample Project

This sample waits for incoming connections on the pre-defined port (acts as a FIX Acceptor).

If the incoming application-level message is 'SingleOrder - New' (MsgType='D') then the 'ExecutionReport' (MsgType='8') message is send to the counterparty. Otherwise the 'Email'(MsgType='C') message is sent back.

This sample shows how to set up SSL/TLS connections on the acceptor side using PEM- or PFX-format certificates.

Use --help, -h, -?, /? to view CLI options and their descriptions.

© Onix Solutions

Source code


using System;
using System.Collections.Generic;
using OnixS.Fix;
using System.Globalization;
using System.IO;
using System.CommandLine;
using OnixS.Fix.Fix44;
using System.Runtime.InteropServices;
using NLog.Extensions.Logging;

namespace SslSellSide
{
    /// <summary>
    /// Waits for incoming connections and processes incoming messages.
    /// </summary>
    internal static class Acceptor
    {
        private const ProtocolVersion fixVersion = ProtocolVersion.Fix44;
        private static int orderCounter;

        private static string GetLicenseStoreFolder()
        {
            string path = Path.Join(AppContext.BaseDirectory, "../../../../../license");

            if (Directory.Exists(path))
                return path;

            // expecting it run after `dotnet publish` using default paths
            return Path.Join(AppContext.BaseDirectory, "../../../../../../license");
        }

        private static System.Security.SecureString MakeSecureString(string input)
        {
            System.Security.SecureString result = new();

            foreach (char c in input)
                result.AppendChar(c);

            return result;
        }

        private static void Run(bool usePemCertificate, bool verifyPeer)
        {
            Console.WriteLine("SSL Sell Side Sample");

            EngineSettings settings = new()
            {
                SendLogoutOnException = true,
                SendLogoutOnInvalidLogon = true, // E.g. to send a Logout when the sequence number of the incoming Logon (A) message is less than expected.
                LicenseStore = GetLicenseStoreFolder(),
                LoggerProvider = new NLogLoggerProvider()
            };

            var ssl = settings.Ssl;

            ssl.ListenPorts.Add(10451);
            ssl.CertificateLocation = usePemCertificate ? "server.pem|server-pk.pem" : "server.p12";

            if(verifyPeer)
            {
                ssl.VerifyPeer = true;
                ssl.CaFile = usePemCertificate ? "cert-chain.pem" : "cert-chain.p12";
            }

            ssl.PrivateKeyPassword = MakeSecureString("OnixS");

            Engine.Init(settings);

            Engine.Instance.Error += (object sender, EngineErrorEventArgs args) =>
            {
                Console.WriteLine("Engine Error: " + args);
            };

            Engine.Instance.Warning += (object sender, EngineWarningEventArgs args) =>
            {
                Console.WriteLine("Engine Warning: " + args);
            };

            var dictionary = fixVersion.ToDictionary();

            const string senderCompID = "SslSellSide";
            const string targetCompID = "SslBuySide";

            using Session session = new(senderCompID, targetCompID, dictionary)
            {
                Encryption = EncryptionMethod.Ssl
            };

            session.InboundApplicationMessage += OnInboundApplicationMessage;

            session.StateChanged += (object sender, SessionStateChangeEventArgs e) =>
            {
                Console.WriteLine("Session state: " + e.NewState.ToString());
            };

            session.Error += (object sender, SessionErrorEventArgs e) =>
            {
                Console.WriteLine("Session Error: " + e);
            };

            session.Warning += (object sender, SessionWarningEventArgs e) =>
            {
                Console.WriteLine("Warning: " + e);
            };

            session.LogonAsAcceptor();

            Console.WriteLine("\nAwaiting SSL session-initiator with"
                              + "\n SenderCompID (49) = " + targetCompID
                              // Note: from the counterparty  point of view SenderCompID is TargetCompID
                              + "\n TargetCompID (56) = " + senderCompID
                              + "\n FIX version = " + fixVersion
                              + "\non port " + Engine.Instance.Settings.Ssl.ListenPorts[0] + " ...");

            while (true)
            {
                Console.Write("Press 'X' to quit");

                // Start a console read operation. Do not display the input.
                ConsoleKeyInfo cki = Console.ReadKey(true);

                // Announce the name of the key that was pressed .
                Console.WriteLine($"  Key pressed: {cki.Key}\n");

                // Exit if the user pressed the 'X' key.
                if (cki.Key == ConsoleKey.X)
                    break;
            }
        }

        private static void OnInboundApplicationMessage(object sender, InboundMessageEventArgs e)
        {
            Console.WriteLine("Received application-level message:\n" + e.Message);

            try
            {
                List<Message> replies = new();
                switch (e.Message.Type)
                {
                    case MsgType.NewOrderSingle:
                        Random rnd = new();
                        int x = rnd.Next(1, 5);
                        switch (x)
                        {
                            case 1:
                                replies.Add(CreateExecutionReport(e.Message, OrdStatus.New, ExecType.New));
                                break;

                            case 2:
                                replies.Add(CreateExecutionReport(e.Message, OrdStatus.New, ExecType.New));
                                replies.Add(CreateExecutionReport(e.Message, OrdStatus.Partial, ExecType.Trade));
                                break;

                            case 3:
                                replies.Add(CreateExecutionReport(e.Message, OrdStatus.New, ExecType.New));
                                replies.Add(CreateExecutionReport(e.Message, OrdStatus.Filled, ExecType.Trade));
                                break;

                            case 4:
                                replies.Add(CreateExecutionReport(e.Message, OrdStatus.Rejected, ExecType.Rejected));
                                break;
                        }
                        break;

                    case MsgType.OrderCancelRequest:
                        replies.Add(CreateCancelledExecutionReport(e.Message));
                        break;

                    default:
                        Message email = new(MsgType.Email, ((Session)sender).Dictionary);
                        email[Tag.EmailThreadID] = "SellSide reply";
                        email[Tag.EmailType] = EmailType.New;
                        email[Tag.Subject] = "Message was received";

                        email[Tag.EmailType] = EmailType.New;
                        Group group = email.SetGroup(Tag.NoLinesOfText, 1);
                        group.Set(Tag.Text, 0, "The message with MsgType=" + e.Message[Tag.MsgType] + " was received");

                        replies.Add(email);
                        break;
                }

                var sn = (Session) sender;

                foreach (Message reply in replies)
                {
                    reply.Validate();
                    sn.Send(reply);
                    Console.WriteLine("Sent to the counterparty:\n" + reply);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception during the processing of the incoming message: " + ex);
            }
        }

        private static Message CreateExecutionReport(Message order, string orderStatus, string executionType)
        {
            Message report = new(MsgType.ExecutionReport, order.Dictionary);

            ++orderCounter;

            report[Tag.ClOrdID] = order[Tag.ClOrdID];
            report[Tag.OrderID] = "OrderID_ " + DateTime.Now.ToString("HHmmss", CultureInfo.InvariantCulture);
            report[Tag.ExecID] = "ExecID_" + orderCounter;
            report[Tag.OrdStatus] = orderStatus;
            report[Tag.ExecType] = executionType;
            report[Tag.Symbol] = order[Tag.Symbol];
            report[Tag.Side] = order[Tag.Side];
            report[Tag.OrdType] = order[Tag.OrdType];
            report[Tag.OrderQty] = order[Tag.OrderQty];
            report[Tag.LeavesQty] = order[Tag.OrderQty];
            report.Set(Tag.CumQty, 0)
                  .Set(Tag.AvgPx, 100.0);

            return report;
        }

        private static Message CreateCancelledExecutionReport(Message cancelRequest)
        {
            Message report = new(MsgType.ExecutionReport, cancelRequest.Dictionary);

            ++orderCounter;

            report[Tag.ClOrdID] = cancelRequest[Tag.ClOrdID];
            report[Tag.OrigClOrdID] = cancelRequest[Tag.OrigClOrdID];
            report[Tag.OrderID] = "OrderID_ " + DateTime.Now.ToString("HHmmss", CultureInfo.InvariantCulture);
            report[Tag.ExecID] = "ExecID_" + orderCounter;
            report[Tag.OrdStatus] = OrdStatus.Canceled;
            report[Tag.ExecType] = ExecType.Canceled;
            report[Tag.Symbol] = cancelRequest[Tag.Symbol];
            report[Tag.Side] = cancelRequest[Tag.Side];
            report[Tag.OrdType] = cancelRequest[Tag.OrdType];
            report[Tag.OrderQty] = cancelRequest[Tag.OrderQty];
            report.Set(Tag.CumQty, 0)
                  .Set(Tag.LeavesQty, 0)
                  .Set(Tag.AvgPx, 0.0);

            return report;
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        private static int Main(string[] args)
        {
            try
            {
                var pemOption = new Option<bool>(
                    new[] { "--use-pem-certificate", "-p" },
                    "Option to use PEM certificate instead of default PFX one");

                var verifyPeerOption = new Option<bool>(
                    new[] { "--verify-peer", "-v" },
                    "Specifies if the server should verify the client certificate");

                var rootCommand = new RootCommand { pemOption, verifyPeerOption };

                rootCommand.Description = "SSL Sell Side Sample";
                rootCommand.TreatUnmatchedTokensAsErrors = true;
                rootCommand.SetHandler<bool, bool>(Run, pemOption, verifyPeerOption);

                return rootCommand.Invoke(args);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: " + ex);

                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && ex.Message.Contains("ErrorCode=10013"))
                {
                    Console.WriteLine("The socket is already in use, or access to the port is restricted on this OS. Please try to change the listen port in EngineSettings.ListenPorts to another one.");
                    Console.WriteLine("You can view a list of which ports are excluded from your user by running this command: 'netsh interface ipv4 show excludedportrange protocol=tcp'");
                }

                return 1;
            }
            finally
            {
                // From https://github.com/NLog/NLog/wiki/Tutorial:
                // NET Application running on Mono / Linux are required to stop threads / timers before entering application shutdown phase.
                // Failing to do this will cause unhandled exceptions and segmentation faults, and other unpredictable behavior.
                NLog.LogManager.Shutdown(); // Flush and close down internal threads and timers
            }
        }
    }
}

In this article
Back to top Copyright © Onix Solutions.
Generated by DocFX