Skip to content

Commit 580759a

Browse files
Better code for authentication with cram.
1 parent d6e734a commit 580759a

1 file changed

Lines changed: 90 additions & 20 deletions

File tree

QuantConnect.DataBento/DataBentoRawLiveClient.cs

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using System.IO;
1919
using System.Net.Sockets;
2020
using System.Text;
21+
using System.Security.Cryptography;
2122
using System.Text.Json;
2223
using System.Threading;
2324
using System.Threading.Tasks;
@@ -116,7 +117,7 @@ public async Task<bool> ConnectAsync()
116117
}
117118

118119
/// <summary>
119-
/// Authenticates with the DataBento gateway
120+
/// Authenticates with the DataBento gateway using CRAM
120121
/// </summary>
121122
private async Task<bool> AuthenticateAsync()
122123
{
@@ -125,35 +126,104 @@ private async Task<bool> AuthenticateAsync()
125126

126127
try
127128
{
128-
// DataBento authentication message format
129-
var authMessage = new
129+
// Read greeting and challenge from gateway
130+
var responseBuffer = new byte[1024];
131+
var messageBuffer = new StringBuilder();
132+
133+
// Read until we have both the version and cram messages
134+
string? version = null;
135+
string? cram = null;
136+
137+
while (version == null || cram == null)
130138
{
131-
msg_type = "authenticate",
132-
api_key = _apiKey,
133-
version = "1.0"
134-
};
135-
136-
var authJson = JsonSerializer.Serialize(authMessage);
137-
var authBytes = Encoding.UTF8.GetBytes(authJson + "\n");
139+
var authenticationBytesRead = await _stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
140+
if (authenticationBytesRead == 0)
141+
{
142+
Log.Error("DatabentoRawClient.AuthenticateAsync(): Connection closed during authentication");
143+
return false;
144+
}
145+
146+
var receivedData = Encoding.UTF8.GetString(responseBuffer, 0, authenticationBytesRead);
147+
messageBuffer.Append(receivedData);
148+
149+
var messages = messageBuffer.ToString().Split('\n', StringSplitOptions.RemoveEmptyEntries);
150+
151+
foreach (var msg in messages)
152+
{
153+
if (msg.StartsWith("lsg_version="))
154+
{
155+
version = msg.Substring("lsg_version=".Length);
156+
Log.Debug($"DatabentoRawClient.AuthenticateAsync(): Gateway version: {version}");
157+
}
158+
else if (msg.StartsWith("cram="))
159+
{
160+
cram = msg.Substring("cram=".Length);
161+
Log.Debug($"DatabentoRawClient.AuthenticateAsync(): Received CRAM challenge");
162+
}
163+
}
164+
}
138165

166+
if (string.IsNullOrEmpty(cram))
167+
{
168+
Log.Error("DatabentoRawClient.AuthenticateAsync(): No CRAM challenge received");
169+
return false;
170+
}
171+
172+
// Generate authentication response
173+
// Concatenate cram and API key: $cram|$key
174+
var cramAndKey = $"{cram}|{_apiKey}";
175+
176+
// Hash with SHA-256
177+
string authHash;
178+
using (var sha256 = SHA256.Create())
179+
{
180+
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(cramAndKey));
181+
authHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
182+
}
183+
184+
// Get last 5 characters of API key (bucket_id)
185+
var bucketId = _apiKey.Substring(_apiKey.Length - 5);
186+
187+
// Create auth response: $hash-$bucket_id
188+
var authResponse = $"{authHash}-{bucketId}";
189+
190+
// Send authentication message
191+
// Format: auth=$authResponse|dataset=GLBX.MDP3|encoding=dbn|ts_out=0\n
192+
var authMessage = $"auth={authResponse}|dataset=GLBX.MDP3|encoding=dbn|ts_out=0\n";
193+
var authBytes = Encoding.UTF8.GetBytes(authMessage);
194+
139195
await _stream.WriteAsync(authBytes, 0, authBytes.Length);
140196
await _stream.FlushAsync();
141-
197+
198+
Log.Debug("DatabentoRawClient.AuthenticateAsync(): Sent authentication message");
199+
142200
// Read authentication response
143-
var responseBuffer = new byte[1024];
201+
messageBuffer.Clear();
144202
var bytesRead = await _stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
145203
var response = Encoding.UTF8.GetString(responseBuffer, 0, bytesRead);
146-
204+
147205
Log.Debug($"DatabentoRawClient.AuthenticateAsync(): Auth response: {response}");
148-
149-
// Parse response to check if authentication was successful
150-
var responseDoc = JsonDocument.Parse(response.Trim());
151-
if (responseDoc.RootElement.TryGetProperty("success", out var successElement))
206+
207+
// Check for success response: success=1|session_id=...
208+
if (response.Contains("success=1"))
152209
{
153-
return successElement.GetBoolean();
210+
// Extract session ID if needed
211+
if (response.Contains("session_id="))
212+
{
213+
var sessionIdStart = response.IndexOf("session_id=") + "session_id=".Length;
214+
var sessionIdEnd = response.IndexOf('|', sessionIdStart);
215+
if (sessionIdEnd == -1) sessionIdEnd = response.IndexOf('\n', sessionIdStart);
216+
if (sessionIdEnd > sessionIdStart)
217+
{
218+
var sessionId = response.Substring(sessionIdStart, sessionIdEnd - sessionIdStart);
219+
Log.Debug($"DatabentoRawClient.AuthenticateAsync(): Session ID: {sessionId}");
220+
}
221+
}
222+
return true;
154223
}
155-
156-
return response.Contains("success") || response.Contains("authenticated");
224+
225+
Log.Trace($"DatabentoRawClient.AuthenticateAsync(): Authentication failed: {response}");
226+
return false;
157227
}
158228
catch (Exception ex)
159229
{

0 commit comments

Comments
 (0)