31
31
32
32
using System ;
33
33
using System . Buffers ;
34
- using System . IO ;
35
- using System . IO . Pipelines ;
36
- using System . Net . Sockets ;
37
34
using System . Runtime . CompilerServices ;
38
- using System . Runtime . ExceptionServices ;
39
- using System . Threading . Tasks ;
40
35
41
36
using RabbitMQ . Client . Exceptions ;
42
37
using RabbitMQ . Util ;
@@ -67,6 +62,16 @@ private static int WriteBaseFrame(Span<byte> span, FrameType type, ushort channe
67
62
return StartPayload + 1 + payloadLength ;
68
63
}
69
64
65
+ internal static class Heartbeat
66
+ {
67
+ public const int FrameSize = BaseFrameSize ;
68
+
69
+ public static int WriteTo ( Span < byte > span )
70
+ {
71
+ return WriteBaseFrame ( span , FrameType . FrameHeartbeat , 0 , 0 ) ;
72
+ }
73
+ }
74
+
70
75
internal static class Method
71
76
{
72
77
/* +----------+-----------+-----------+
@@ -130,23 +135,6 @@ public static int WriteTo(Span<byte> span, ushort channel, ReadOnlySpan<byte> bo
130
135
return WriteBaseFrame ( span , FrameType . FrameBody , channel , StartBodyArgument - StartPayload + body . Length ) ;
131
136
}
132
137
}
133
-
134
- internal static class Heartbeat
135
- {
136
- /* Empty frame */
137
- public const int FrameSize = BaseFrameSize ;
138
-
139
- /// <summary>
140
- /// Compiler trick to directly refer to static data in the assembly, see here: https://github.com/dotnet/roslyn/pull/24621
141
- /// </summary>
142
- internal static ReadOnlySpan < byte > Payload => new byte [ ]
143
- {
144
- Constants . FrameHeartbeat ,
145
- 0 , 0 , // channel
146
- 0 , 0 , 0 , 0 , // payload length
147
- Constants . FrameEnd
148
- } ;
149
- }
150
138
}
151
139
152
140
internal readonly struct InboundFrame
@@ -164,96 +152,88 @@ private InboundFrame(FrameType type, int channel, ReadOnlyMemory<byte> payload,
164
152
_rentedArray = rentedArray ;
165
153
}
166
154
167
- private static async ValueTask ProcessProtocolHeader ( PipeReader reader , ReadOnlyMemory < byte > protocolError )
155
+ private static void ProcessProtocolHeader ( ReadOnlySpan < byte > protocolError )
168
156
{
169
- try
170
- {
171
- byte b1 = protocolError . Span [ 0 ] ;
172
- byte b2 = protocolError . Span [ 1 ] ;
173
- byte b3 = protocolError . Span [ 2 ] ;
174
- if ( b1 != 'M' || b2 != 'Q' || b3 != 'P' )
175
- {
176
- throw new MalformedFrameException ( "Invalid AMQP protocol header from server" ) ;
177
- }
178
-
179
- int transportHigh = protocolError . Span [ 3 ] ;
180
- int transportLow = protocolError . Span [ 4 ] ;
181
- int serverMajor = protocolError . Span [ 5 ] ;
182
- int serverMinor = await reader . ReadByteAsync ( ) . ConfigureAwait ( false ) ; ;
183
- throw new PacketNotRecognizedException ( transportHigh , transportLow , serverMajor , serverMinor ) ;
184
- }
185
- catch ( EndOfStreamException )
157
+ byte b1 = protocolError [ 0 ] ;
158
+ byte b2 = protocolError [ 1 ] ;
159
+ byte b3 = protocolError [ 2 ] ;
160
+ if ( b1 != 'M' || b2 != 'Q' || b3 != 'P' )
186
161
{
187
- // Ideally we'd wrap the EndOfStreamException in the
188
- // MalformedFrameException, but unfortunately the
189
- // design of MalformedFrameException's superclass,
190
- // ProtocolViolationException, doesn't permit
191
- // this. Fortunately, the call stack in the
192
- // EndOfStreamException is largely irrelevant at this
193
- // point, so can safely be ignored.
194
162
throw new MalformedFrameException ( "Invalid AMQP protocol header from server" ) ;
195
163
}
164
+
165
+ int transportHigh = protocolError [ 3 ] ;
166
+ int transportLow = protocolError [ 4 ] ;
167
+ int serverMajor = protocolError [ 5 ] ;
168
+ int serverMinor = protocolError [ 6 ] ;
169
+ throw new PacketNotRecognizedException ( transportHigh , transportLow , serverMajor , serverMinor ) ;
196
170
}
197
171
198
- internal static async ValueTask < InboundFrame > ReadFrom ( PipeReader reader , Memory < byte > frameHeaderBuffer )
172
+ internal static bool TryReadFrame ( ref ReadOnlySequence < byte > buffer , out InboundFrame frame )
199
173
{
200
- // We'll always need to read at least 7 bytes (type + channel + payloadSize) or (type + first 6 bytes of protocol error, see ProcessProtocolHeader).
201
- int type ;
202
- try
174
+ // We'll always need to read at least 8 bytes (type (1) + channel (2) + payloadSize (4) + end marker (1)) or (8 bytes of protocol error, see ProcessProtocolHeader).
175
+ if ( buffer . Length < 8 )
203
176
{
204
- await reader . ReadAsync ( frameHeaderBuffer . Slice ( 0 , 7 ) ) . ConfigureAwait ( false ) ;
205
-
206
- type = frameHeaderBuffer . Span [ 0 ] ;
177
+ frame = default ;
178
+ return false ;
207
179
}
208
- catch ( IOException ioe )
180
+
181
+ if ( buffer . First . Span [ 0 ] == 'A' )
209
182
{
210
- // If it's a WSAETIMEDOUT SocketException, unwrap it.
211
- // This might happen when the limit of half-open connections is
212
- // reached.
213
- if ( ioe . InnerException is SocketException exception && exception . SocketErrorCode == SocketError . TimedOut )
183
+ // Probably an AMQP protocol header, otherwise meaningless
184
+ if ( buffer . First . Length >= 8 )
214
185
{
215
- ExceptionDispatchInfo . Capture ( exception ) . Throw ( ) ;
186
+ ProcessProtocolHeader ( buffer . First . Span . Slice ( 1 , 7 ) ) ;
187
+ }
188
+ else
189
+ {
190
+ Span < byte > protocolError = stackalloc byte [ 7 ] ;
191
+ buffer . Slice ( 1 , 7 ) . CopyTo ( protocolError ) ;
192
+ ProcessProtocolHeader ( protocolError ) ;
216
193
}
217
-
218
- throw ;
219
194
}
220
195
221
- switch ( type )
196
+ int type = buffer . First . Span [ 0 ] ;
197
+ int channel ;
198
+ int payloadSize ;
199
+
200
+ if ( buffer . First . Length >= 7 )
222
201
{
223
- case - 1 :
224
- throw new EndOfStreamException ( "Reached the end of the stream. Possible authentication failure." ) ;
225
- case 'A' :
226
- // Probably an AMQP protocol header, otherwise meaningless
227
- await ProcessProtocolHeader ( reader , frameHeaderBuffer . Slice ( 1 , 6 ) ) . ConfigureAwait ( false ) ;
228
- break ;
202
+ channel = NetworkOrderDeserializer . ReadUInt16 ( buffer . First . Span . Slice ( 1 , 2 ) ) ;
203
+ payloadSize = NetworkOrderDeserializer . ReadInt32 ( buffer . First . Span . Slice ( 3 , 4 ) ) ; // FIXME - throw exn on unreasonable value
204
+ }
205
+ else
206
+ {
207
+ Span < byte > headerBytes = stackalloc byte [ 6 ] ;
208
+ buffer . Slice ( 1 , 6 ) . CopyTo ( headerBytes ) ;
209
+ channel = NetworkOrderDeserializer . ReadUInt16 ( headerBytes . Slice ( 0 , 2 ) ) ;
210
+ payloadSize = NetworkOrderDeserializer . ReadInt32 ( headerBytes . Slice ( 2 , 4 ) ) ; // FIXME - throw exn on unreasonable value
229
211
}
230
-
231
- int channel = NetworkOrderDeserializer . ReadUInt16 ( frameHeaderBuffer . Span . Slice ( 1 , 2 ) ) ;
232
- int payloadSize = NetworkOrderDeserializer . ReadInt32 ( frameHeaderBuffer . Span . Slice ( 3 , 4 ) ) ; // FIXME - throw exn on unreasonable value
233
212
234
213
const int EndMarkerLength = 1 ;
235
- // Is returned by InboundFrame.ReturnPayload in Connection.MainLoopIteration
236
214
int readSize = payloadSize + EndMarkerLength ;
237
- byte [ ] payloadBytes = ArrayPool < byte > . Shared . Rent ( readSize ) ;
238
- Memory < byte > payloadMemory = payloadBytes . AsMemory ( 0 , readSize ) ;
239
- try
240
- {
241
- await reader . ReadAsync ( payloadMemory ) . ConfigureAwait ( false ) ;
242
- }
243
- catch ( Exception e )
215
+
216
+ // Do we have enough bytes to read an entire frame (type + channel + payloadSize + payload + end marker)
217
+ if ( buffer . Length < ( 7 + readSize ) )
244
218
{
245
- // Early EOF.
246
- ArrayPool < byte > . Shared . Return ( payloadBytes ) ;
247
- throw new MalformedFrameException ( $ "Short frame - expected to read { readSize } bytes: { e . Message } ") ;
219
+ frame = default ;
220
+ return false ;
248
221
}
249
222
223
+ // Is returned by InboundFrame.ReturnPayload in Connection.MainLoopIteration
224
+ byte [ ] payloadBytes = ArrayPool < byte > . Shared . Rent ( readSize ) ;
225
+ Memory < byte > payloadMemory = payloadBytes . AsMemory ( 0 , readSize ) ;
226
+ ReadOnlySequence < byte > payloadSlice = buffer . Slice ( 7 , readSize ) ;
227
+ payloadSlice . CopyTo ( payloadMemory . Span ) ;
250
228
if ( payloadBytes [ payloadSize ] != Constants . FrameEnd )
251
229
{
252
230
ArrayPool < byte > . Shared . Return ( payloadBytes ) ;
253
231
throw new MalformedFrameException ( $ "Bad frame end marker: { payloadBytes [ payloadSize ] } ") ;
254
232
}
255
233
256
- return new InboundFrame ( ( FrameType ) type , channel , payloadMemory . Slice ( 0 , payloadSize ) , payloadBytes ) ;
234
+ buffer = buffer . Slice ( payloadSlice . End ) ;
235
+ frame = new InboundFrame ( ( FrameType ) type , channel , payloadMemory . Slice ( 0 , payloadSize ) , payloadBytes ) ;
236
+ return true ;
257
237
}
258
238
259
239
public byte [ ] TakeoverPayload ( )
0 commit comments