using System.Text; using System.Text.Json; using System.Linq; using ZB.MOM.NatsNet.Server.Internal; using ZB.MOM.NatsNet.Server.Internal.DataStructures; namespace ZB.MOM.NatsNet.Server; internal sealed partial class NatsStream { internal static (string Ttl, bool Ok) GetMessageScheduleTTL(byte[]? hdr) { if (hdr == null || hdr.Length == 0) return (string.Empty, true); var ttl = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsScheduleTtl, hdr); if (ttl == null || ttl.Length == 0) return (string.Empty, true); var ttlValue = Encoding.ASCII.GetString(ttl); var (_, err) = ParseMessageTTL(ttlValue); return err == null ? (ttlValue, true) : (string.Empty, false); } internal static string GetMessageScheduleTarget(byte[]? hdr) { if (hdr == null || hdr.Length == 0) return string.Empty; var value = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsScheduleTarget, hdr); return value == null || value.Length == 0 ? string.Empty : Encoding.ASCII.GetString(value); } internal static string GetMessageScheduleSource(byte[]? hdr) { if (hdr == null || hdr.Length == 0) return string.Empty; var value = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsScheduleSource, hdr); return value == null || value.Length == 0 ? string.Empty : Encoding.ASCII.GetString(value); } internal static string GetBatchId(byte[]? hdr) { if (hdr == null || hdr.Length == 0) return string.Empty; var value = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsBatchId, hdr); return value == null || value.Length == 0 ? string.Empty : Encoding.ASCII.GetString(value); } internal static (ulong Seq, bool Exists) GetBatchSequence(byte[]? hdr) { if (hdr == null || hdr.Length == 0) return (0, false); var value = NatsMessageHeaders.SliceHeader(NatsHeaderConstants.JsBatchSeq, hdr); if (value is null || value.Value.Length == 0) return (0, false); var parsed = ServerUtilities.ParseInt64(value.Value.Span); return parsed < 0 ? (0, false) : ((ulong)parsed, true); } public bool IsClustered() { _mu.EnterReadLock(); try { return IsClusteredInternal(); } finally { _mu.ExitReadLock(); } } internal bool IsClusteredInternal() => _node is IRaftNode; internal void QueueInbound(IpQueue inbound, string subject, string? reply, byte[]? hdr, byte[]? msg, object? sourceInfo, object? trace) { _ = sourceInfo; _ = trace; var inboundMsg = InMsg.Rent(); inboundMsg.Subject = subject; inboundMsg.Reply = reply; inboundMsg.Hdr = hdr; inboundMsg.Msg = msg; var (_, error) = inbound.Push(inboundMsg); if (error != null) inboundMsg.ReturnToPool(); } internal JsApiMsgGetResponse ProcessDirectGetRequest(string reply, byte[]? hdr, byte[]? msg) { var response = new JsApiMsgGetResponse(); if (string.IsNullOrWhiteSpace(reply)) return response; if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiLevelHeader(hdr))) { response.Error = JsApiErrors.NewJSRequiredApiLevelError(); return response; } if (msg == null || msg.Length == 0) { response.Error = JsApiErrors.NewJSBadRequestError(); return response; } JsApiMsgGetRequest? request; try { request = JsonSerializer.Deserialize(msg); } catch (JsonException) { response.Error = JsApiErrors.NewJSBadRequestError(); return response; } if (request == null) { response.Error = JsApiErrors.NewJSBadRequestError(); return response; } return GetDirectRequest(request, reply); } internal JsApiMsgGetResponse ProcessDirectGetLastBySubjectRequest(string subject, string reply, byte[]? hdr, byte[]? msg) { var response = new JsApiMsgGetResponse(); if (string.IsNullOrWhiteSpace(reply)) return response; if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiLevelHeader(hdr))) { response.Error = JsApiErrors.NewJSRequiredApiLevelError(); return response; } var request = msg == null || msg.Length == 0 ? new JsApiMsgGetRequest() : JsonSerializer.Deserialize(msg) ?? new JsApiMsgGetRequest(); var key = ExtractDirectGetLastBySubjectKey(subject); if (string.IsNullOrEmpty(key)) { response.Error = JsApiErrors.NewJSBadRequestError(); return response; } request.LastFor = key; return GetDirectRequest(request, reply); } internal JsApiMsgGetResponse GetDirectMulti(JsApiMsgGetRequest request, string reply) { _ = reply; if (Store == null) return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() }; var filters = request.MultiLastFor ?? []; if (filters.Length == 0) return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() }; var (seqs, error) = Store.MultiLastSeqs(filters, request.UpToSeq, 1024); if (error != null || seqs.Length == 0) return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() }; var firstSeq = seqs[0]; var loaded = Store.LoadMsg(firstSeq, new StoreMsg()); if (loaded == null) return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() }; return new JsApiMsgGetResponse { Message = ToStoredMsg(loaded) }; } internal JsApiMsgGetResponse GetDirectRequest(JsApiMsgGetRequest request, string reply) { _ = reply; if (request.MultiLastFor is { Length: > 0 }) return GetDirectMulti(request, reply); if (Store == null) return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() }; StoreMsg? loaded = null; if (request.Seq > 0) { loaded = Store.LoadMsg(request.Seq, new StoreMsg()); } else if (!string.IsNullOrWhiteSpace(request.LastFor)) { loaded = Store.LoadLastMsg(request.LastFor!, new StoreMsg()); } else if (!string.IsNullOrWhiteSpace(request.NextFor)) { var (sm, _) = Store.LoadNextMsg(request.NextFor!, SubscriptionIndex.SubjectHasWildcard(request.NextFor!), request.Seq, new StoreMsg()); loaded = sm; } else if (request.StartTime.HasValue) { var seq = Store.GetSeqFromTime(request.StartTime.Value); if (seq > 0) loaded = Store.LoadMsg(seq, new StoreMsg()); } if (loaded == null) return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() }; return new JsApiMsgGetResponse { Message = ToStoredMsg(loaded) }; } private static StoredMsg ToStoredMsg(StoreMsg loaded) => new() { Subject = loaded.Subject, Sequence = loaded.Seq, Header = loaded.Hdr, Data = loaded.Msg, Time = DateTimeOffset.FromUnixTimeMilliseconds(loaded.Ts / 1_000_000L).UtcDateTime, }; private static string? GetRequiredApiLevelHeader(byte[]? hdr) { if (hdr == null || hdr.Length == 0) return null; var value = NatsMessageHeaders.GetHeader(JsApiSubjects.JsRequiredApiLevel, hdr); return value == null || value.Length == 0 ? null : Encoding.ASCII.GetString(value); } private static string ExtractDirectGetLastBySubjectKey(string subject) { if (string.IsNullOrWhiteSpace(subject)) return string.Empty; var parts = subject.Split('.'); return parts.Length <= 5 ? string.Empty : string.Join('.', parts.Skip(5)); } }