1
1
using System . Buffers ;
2
2
using System . Diagnostics . CodeAnalysis ;
3
+ using System . Xml . Linq ;
3
4
using NATS . Client . Core . Internal ;
4
5
5
6
namespace NATS . Client . Core ;
6
7
8
+ [ Flags ]
9
+ public enum NatsMsgFlags : byte
10
+ {
11
+ None = 0 ,
12
+ Empty = 1 ,
13
+ NoResponders = 2 ,
14
+ }
15
+
7
16
/// <summary>
8
17
/// This interface provides an optional contract when passing
9
18
/// messages to processing methods which is usually helpful in
@@ -103,12 +112,6 @@ public interface INatsMsg<T>
103
112
/// <summary>
104
113
/// NATS message structure as defined by the protocol.
105
114
/// </summary>
106
- /// <param name="Subject">The destination subject to publish to.</param>
107
- /// <param name="ReplyTo">The reply subject that subscribers can use to send a response back to the publisher/requester.</param>
108
- /// <param name="Size">Message size in bytes.</param>
109
- /// <param name="Headers">Pass additional information using name-value pairs.</param>
110
- /// <param name="Data">Serializable data object.</param>
111
- /// <param name="Connection">NATS connection this message is associated to.</param>
112
115
/// <typeparam name="T">Specifies the type of data that may be sent to the NATS Server.</typeparam>
113
116
/// <remarks>
114
117
/// <para>Connection property is used to provide reply functionality.</para>
@@ -119,20 +122,120 @@ public interface INatsMsg<T>
119
122
/// </code>
120
123
/// </para>
121
124
/// </remarks>
122
- public readonly record struct NatsMsg < T > (
123
- string Subject ,
124
- string ? ReplyTo ,
125
- int Size ,
126
- NatsHeaders ? Headers ,
127
- T ? Data ,
128
- INatsConnection ? Connection ) : INatsMsg < T >
125
+ public readonly record struct NatsMsg < T > : INatsMsg < T >
129
126
{
127
+ /*
128
+ 2 30
129
+ +--+------------------------------+
130
+ |EN| Message Size |
131
+ +--+------------------------------+
132
+ E: Empty flag
133
+ N: No responders flag
134
+
135
+ # Size is 30 bits:
136
+ Max Size: 1,073,741,823 (0x3FFFFFFF / 00111111111111111111111111111111)
137
+ Uint.Max: 4,294,967,295
138
+ Int.Max: 2,147,483,647
139
+ 8mb: 8,388,608
140
+ */
141
+ private readonly uint _flagsAndSize ;
142
+
143
+ /// <summary>
144
+ /// NATS message structure as defined by the protocol.
145
+ /// </summary>
146
+ /// <param name="subject">The destination subject to publish to.</param>
147
+ /// <param name="replyTo">The reply subject that subscribers can use to send a response back to the publisher/requester.</param>
148
+ /// <param name="size">Message size in bytes.</param>
149
+ /// <param name="headers">Pass additional information using name-value pairs.</param>
150
+ /// <param name="data">Serializable data object.</param>
151
+ /// <param name="connection">NATS connection this message is associated to.</param>
152
+ /// <param name="flags">Message flags to indicate no responders and empty payloads.</param>
153
+ /// <remarks>
154
+ /// <para>Connection property is used to provide reply functionality.</para>
155
+ /// <para>
156
+ /// Message size is calculated using the same method NATS server uses:
157
+ /// <code lang="C#">
158
+ /// int size = subject.Length + replyTo.Length + headers.Length + payload.Length;
159
+ /// </code>
160
+ /// </para>
161
+ /// </remarks>
162
+ public NatsMsg (
163
+ string subject ,
164
+ string ? replyTo ,
165
+ int size ,
166
+ NatsHeaders ? headers ,
167
+ T ? data ,
168
+ INatsConnection ? connection ,
169
+ NatsMsgFlags flags = default )
170
+ {
171
+ Subject = subject ;
172
+ ReplyTo = replyTo ;
173
+ _flagsAndSize = ( ( uint ) flags << 30 ) | ( uint ) ( size & 0x3FFFFFFF ) ;
174
+ Headers = headers ;
175
+ Data = data ;
176
+ Connection = connection ;
177
+ }
178
+
130
179
/// <inheritdoc />
131
180
public NatsException ? Error => Headers ? . Error ;
132
181
182
+ /// <summary>The destination subject to publish to.</summary>
183
+ public string Subject { get ; init ; }
184
+
185
+ /// <summary>The reply subject that subscribers can use to send a response back to the publisher/requester.</summary>
186
+ public string ? ReplyTo { get ; init ; }
187
+
188
+ /// <summary>Message size in bytes.</summary>
189
+ public int Size
190
+ {
191
+ // Extract the lower 30 bits
192
+ get => ( int ) ( _flagsAndSize & 0x3FFFFFFF ) ;
193
+
194
+ // Clear the lower 30 bits and set the new number
195
+ init
196
+ {
197
+ // Mask the input value to fit within 30 bits (clear upper bits)
198
+ var numberPart = ( uint ) ( value & 0x3FFFFFFF ) ;
199
+
200
+ // Clear the lower 30 bits and set the new number value
201
+ // Preserve the flags, update the number
202
+ _flagsAndSize = ( _flagsAndSize & 0xC0000000 ) | numberPart ;
203
+ }
204
+ }
205
+
206
+ public NatsMsgFlags Flags
207
+ {
208
+ // Extract the two leftmost bits (31st and 30th bit)
209
+ // Mask with 0b11 to get two bits
210
+ get => ( NatsMsgFlags ) ( ( _flagsAndSize >> 30 ) & 0b11 ) ;
211
+
212
+ init
213
+ {
214
+ // Clear the current flag bits (set to 0) and then set the new flag value
215
+ var flagsPart = ( uint ) value << 30 ;
216
+ _flagsAndSize = ( _flagsAndSize & 0x3FFFFFFF ) | flagsPart ;
217
+ }
218
+ }
219
+
220
+ /// <summary>Pass additional information using name-value pairs.</summary>
221
+ public NatsHeaders ? Headers { get ; init ; }
222
+
223
+ /// <summary>Serializable data object.</summary>
224
+ public T ? Data { get ; init ; }
225
+
226
+ /// <summary>NATS connection this message is associated to.</summary>
227
+ public INatsConnection ? Connection { get ; init ; }
228
+
229
+ public bool IsEmpty => ( _flagsAndSize & 0x40000000 ) != 0 ;
230
+
231
+ public bool HasNoResponders => ( _flagsAndSize & 0x80000000 ) != 0 ;
232
+
133
233
/// <inheritdoc />
134
234
public void EnsureSuccess ( )
135
235
{
236
+ if ( HasNoResponders )
237
+ throw new NatsNoRespondersException ( ) ;
238
+
136
239
if ( Error != null )
137
240
throw Error ;
138
241
}
@@ -197,6 +300,17 @@ public ValueTask ReplyAsync<TReply>(NatsMsg<TReply> msg, INatsSerialize<TReply>?
197
300
return Connection . PublishAsync ( msg with { Subject = ReplyTo } , serializer , opts , cancellationToken ) ;
198
301
}
199
302
303
+ public void Deconstruct ( out string subject , out string ? replyTo , out int size , out NatsHeaders ? headers , out T ? data , out INatsConnection ? connection , out NatsMsgFlags flags )
304
+ {
305
+ subject = Subject ;
306
+ replyTo = ReplyTo ;
307
+ size = Size ;
308
+ headers = Headers ;
309
+ data = Data ;
310
+ connection = Connection ;
311
+ flags = Flags ;
312
+ }
313
+
200
314
internal static NatsMsg < T > Build (
201
315
string subject ,
202
316
string ? replyTo ,
@@ -207,6 +321,16 @@ internal static NatsMsg<T> Build(
207
321
INatsDeserialize < T > serializer )
208
322
{
209
323
NatsHeaders ? headers = null ;
324
+ var flags = NatsMsgFlags . None ;
325
+
326
+ if ( payloadBuffer . Length == 0 )
327
+ {
328
+ flags |= NatsMsgFlags . Empty ;
329
+ if ( NatsSubBase . IsHeader503 ( headersBuffer ) )
330
+ {
331
+ flags |= NatsMsgFlags . NoResponders ;
332
+ }
333
+ }
210
334
211
335
if ( headersBuffer != null )
212
336
{
@@ -277,7 +401,7 @@ internal static NatsMsg<T> Build(
277
401
}
278
402
}
279
403
280
- return new NatsMsg < T > ( subject , replyTo , ( int ) size , headers , data , connection ) ;
404
+ return new NatsMsg < T > ( subject , replyTo , ( int ) size , headers , data , connection , flags ) ;
281
405
}
282
406
283
407
[ MemberNotNull ( nameof ( Connection ) ) ]
0 commit comments