using SierraChartDTC.Helpers; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Sockets; using System.Runtime.InteropServices.JavaScript; using System.Text; using System.Text.Json; using System.Threading.Tasks; using System.Windows.Shapes; using HeartbeatTimer = System.Timers.Timer; namespace SierraChartDTC.Connection; // Basic Sierra chart DTC Connection that authorizes and sends heartbeats as well as listens to any order updates public class SierraChartConnection { public List Messages { get; set; } public event EventHandler? LogMessage; public event EventHandler? TradeUpdateReceived; public event EventHandler? SierraChartConnected; private static readonly string SierraServerIP = "127.0.0.1"; private static readonly int SierraServerPort = 11099; private CancellationTokenSource _cts; private TcpClient _client; private NetworkStream _stream; private HeartbeatTimer _heartbeatTimer; public SierraChartConnection() { Messages = new List(); SetupHeartbeatTimer(); } public void Disconnect() { _cts.Cancel(); _stream?.Dispose(); _client?.Close(); _heartbeatTimer?.Stop(); LogMessage?.Invoke(this, new LogEntry("Sierra Chart Disconnected")); } #region Connection To DTC Server & Setup Message Receipt private void SetupHeartbeatTimer() { _heartbeatTimer = new HeartbeatTimer(60000); _heartbeatTimer.Elapsed += (sender, e) => { if (_client?.Connected == true) { string heartbeatMsg = DTCProtocol.Heartbeat(); byte[] bytes = System.Text.Encoding.UTF8.GetBytes(heartbeatMsg); _stream.Write(bytes, 0, bytes.Length); _stream.Flush(); } }; _heartbeatTimer.AutoReset = true; } public async Task StartConnection() { _cts = new CancellationTokenSource(); _client = new TcpClient(); _client.Connect(SierraServerIP, SierraServerPort); _stream = _client.GetStream(); LogMessage?.Invoke(this, new LogEntry("Sierra Chart Connected", LogEntryType.Success)); // Authentication Procedure string logonReq = DTCProtocol.LogonRequest(); byte[] bytes = System.Text.Encoding.UTF8.GetBytes(logonReq); await _stream.WriteAsync(bytes, 0, bytes.Length); await _stream.FlushAsync(); _heartbeatTimer.Start(); byte[] buffer = new byte[8192]; var sb = new StringBuilder(); while (!_cts.Token.IsCancellationRequested) { if (_stream.DataAvailable) { int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length, _cts.Token); if (bytesRead == 0) break; // connection closed sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); string allData = sb.ToString(); int nullIndex; while ((nullIndex = allData.IndexOf('\0')) >= 0) { string message = allData.Substring(0, nullIndex); // up to the \0 allData = allData.Substring(nullIndex + 1); // remaining data if (!string.IsNullOrWhiteSpace(message)) { try { HandleDTCMessage(message); } catch (JsonException ex) { LogMessage?.Invoke(this, new LogEntry($"Failed to parse JSON: {ex}", LogEntryType.Rejected)); } } } // Keep any partial message that hasn't ended with \0 yet sb.Clear(); sb.Append(allData); } } } #endregion #region DTC Message Handling private void HandleDTCMessage(string DTCMessage) { var res = JsonDocument.Parse(DTCMessage).RootElement; if (res.TryGetProperty("Type", out JsonElement typeElem) && typeElem.TryGetInt32(out int type)) { if (type == 301) { var update = JsonSerializer.Deserialize(DTCMessage); TradeUpdateReceived?.Invoke(this, update); } else if (type == 2) { SierraChartConnected?.Invoke(this, true); LogMessage?.Invoke(this, new LogEntry("Sierra Chart DTC Logon Successful", LogEntryType.Success)); } } } #endregion }