3
3
4
4
using System ;
5
5
using System . Buffers ;
6
+ using System . Buffers . Text ;
6
7
using System . Collections ;
7
8
using System . Collections . Generic ;
8
9
using System . Diagnostics ;
@@ -33,8 +34,11 @@ internal sealed class Utf8JsonWriterFuzzer : IFuzzer
33
34
private const byte NewLineFlag = 1 << 3 ;
34
35
private const byte SkipValidationFlag = 1 << 4 ;
35
36
36
- // Options for choosing between UTF-8 and UTF-16 encoding
37
- private const byte EncodingFlag = 1 << 5 ;
37
+ // Options for choosing between base64, UTF-8 and UTF-16 encoding
38
+ private const byte EncodingMask = 0b11 << 5 ;
39
+ private const byte Utf8EncodingFlag = 0b00 << 5 ;
40
+ private const byte Utf16EncodingFlag = 0b01 << 5 ;
41
+ private const byte Base64EncodingFlag = 0b10 << 5 ;
38
42
39
43
public void FuzzTarget ( ReadOnlySpan < byte > bytes )
40
44
{
@@ -53,8 +57,13 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
53
57
ReadOnlySpan < char > chars = MemoryMarshal . Cast < byte , char > ( bytes ) ;
54
58
55
59
// Validate that the indices are within bounds of the input
56
- bool utf8 = ( optionsByte & EncodingFlag ) == 0 ;
57
- if ( ! ( 0 <= slice1 && slice1 <= slice2 && slice2 <= ( utf8 ? bytes . Length : chars . Length ) ) )
60
+ int encoding = optionsByte & EncodingMask ;
61
+ if ( encoding is not Utf8EncodingFlag and not Utf16EncodingFlag and not Base64EncodingFlag )
62
+ {
63
+ return ;
64
+ }
65
+
66
+ if ( ! ( 0 <= slice1 && slice1 <= slice2 && slice2 <= ( encoding is Utf16EncodingFlag ? chars . Length : bytes . Length ) ) )
58
67
{
59
68
return ;
60
69
}
@@ -63,7 +72,7 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
63
72
bool indented = ( optionsByte & IndentFlag ) == 0 ;
64
73
JsonWriterOptions options = new ( )
65
74
{
66
- Encoder = ( optionsByte & EncodingFlag ) == 0 ? JavaScriptEncoder . Default : JavaScriptEncoder . UnsafeRelaxedJsonEscaping ,
75
+ Encoder = ( optionsByte & EncoderFlag ) == 0 ? JavaScriptEncoder . Default : JavaScriptEncoder . UnsafeRelaxedJsonEscaping ,
67
76
Indented = indented ,
68
77
MaxDepth = ( optionsByte & MaxDepthFlag ) == 0 ? 1 : 0 ,
69
78
NewLine = ( optionsByte & NewLineFlag ) == 0 ? "\n " : "\r \n " ,
@@ -74,9 +83,9 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
74
83
int maxExpandedSizeBytes = 6 * bytes . Length + 2 ;
75
84
byte [ ] expectedBuffer = ArrayPool < byte > . Shared . Rent ( maxExpandedSizeBytes ) ;
76
85
Span < byte > expected =
77
- expectedBuffer . AsSpan ( 0 , utf8
78
- ? EncodeToUtf8 ( bytes , expectedBuffer , options . Encoder )
79
- : EncodeToUtf8 ( chars , expectedBuffer , options . Encoder ) ) ;
86
+ expectedBuffer . AsSpan ( 0 , encoding == Utf16EncodingFlag
87
+ ? EncodeToUtf8 ( chars , expectedBuffer , options . Encoder )
88
+ : EncodeToUtf8 ( bytes , expectedBuffer , options . Encoder , encoding == Base64EncodingFlag ) ) ;
80
89
81
90
// Compute the actual result by using Utf8JsonWriter. Each iteration is a different slice of the input, but the result should be the same.
82
91
byte [ ] actualBuffer = new byte [ expected . Length ] ;
@@ -89,14 +98,14 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
89
98
{
90
99
using MemoryStream stream = new ( actualBuffer ) ;
91
100
using Utf8JsonWriter writer = new ( stream , options ) ;
92
-
93
- if ( utf8 )
101
+
102
+ if ( encoding == Utf16EncodingFlag )
94
103
{
95
- WriteStringValueSegments ( writer , bytes , ranges ) ;
104
+ WriteStringValueSegments ( writer , chars , ranges ) ;
96
105
}
97
106
else
98
107
{
99
- WriteStringValueSegments ( writer , chars , ranges ) ;
108
+ WriteStringValueSegments ( writer , bytes , ranges , encoding == Base64EncodingFlag ) ;
100
109
}
101
110
102
111
writer . Flush ( ) ;
@@ -110,7 +119,7 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
110
119
}
111
120
112
121
// Additional test for mixing UTF-8 and UTF-16 encoding. The alignment math is easier in UTF-16 mode so just run it for that.
113
- if ( ! utf8 )
122
+ if ( encoding == Utf16EncodingFlag )
114
123
{
115
124
Array . Clear ( expectedBuffer ) ;
116
125
@@ -124,9 +133,16 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
124
133
using MemoryStream stream = new ( actualBuffer ) ;
125
134
using Utf8JsonWriter writer = new ( stream , options ) ;
126
135
136
+ // UTF-16 + UTF-8
127
137
writer . WriteStringValueSegment ( firstSegment , false ) ;
128
-
129
138
Assert . Throws < InvalidOperationException , ReadOnlySpan < byte > > ( state => writer . WriteStringValueSegment ( state , true ) , secondSegment ) ;
139
+
140
+ stream . Position = 0 ;
141
+ writer . Reset ( ) ;
142
+
143
+ // UTF-16 + Base64
144
+ writer . WriteStringValueSegment ( firstSegment , false ) ;
145
+ Assert . Throws < InvalidOperationException , ReadOnlySpan < byte > > ( state => writer . WriteBase64StringSegment ( state , true ) , secondSegment ) ;
130
146
}
131
147
132
148
Array . Clear ( expectedBuffer ) ;
@@ -135,25 +151,67 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
135
151
ReadOnlySpan < byte > firstSegment = bytes [ 0 ..( 2 * slice1 ) ] ;
136
152
ReadOnlySpan < char > secondSegment = chars [ slice1 ..] ;
137
153
138
- expected = expectedBuffer . AsSpan ( 0 , EncodeToUtf8 ( firstSegment , secondSegment , expectedBuffer , options . Encoder ) ) ;
154
+ expected = expectedBuffer . AsSpan ( 0 , EncodeToUtf8 ( firstSegment , expectedBuffer , options . Encoder , base64Encode : false ) ) ;
139
155
140
156
actualBuffer = new byte [ expected . Length ] ;
141
157
using MemoryStream stream = new ( actualBuffer ) ;
142
158
using Utf8JsonWriter writer = new ( stream , options ) ;
143
159
160
+ // UTF-8 + UTF-16
144
161
writer . WriteStringValueSegment ( firstSegment , false ) ;
145
162
Assert . Throws < InvalidOperationException , ReadOnlySpan < char > > ( state => writer . WriteStringValueSegment ( state , true ) , secondSegment ) ;
163
+
164
+ stream . Position = 0 ;
165
+ writer . Reset ( ) ;
166
+
167
+ // UTF-8 + Base64
168
+ writer . WriteStringValueSegment ( firstSegment , false ) ;
169
+ Assert . Throws < InvalidOperationException , ReadOnlySpan < byte > > ( state => writer . WriteBase64StringSegment ( state , true ) , MemoryMarshal . AsBytes ( secondSegment ) ) ;
170
+ }
171
+
172
+ Array . Clear ( expectedBuffer ) ;
173
+
174
+ {
175
+ ReadOnlySpan < byte > firstSegment = bytes [ 0 ..( 2 * slice1 ) ] ;
176
+ ReadOnlySpan < char > secondSegment = chars [ slice1 ..] ;
177
+
178
+ expected = expectedBuffer . AsSpan ( 0 , EncodeToUtf8 ( firstSegment , expectedBuffer , options . Encoder , base64Encode : true ) ) ;
179
+
180
+ actualBuffer = new byte [ expected . Length ] ;
181
+ using MemoryStream stream = new ( actualBuffer ) ;
182
+ using Utf8JsonWriter writer = new ( stream , options ) ;
183
+
184
+ // Base64 + UTF-16
185
+ writer . WriteBase64StringSegment ( firstSegment , false ) ;
186
+ Assert . Throws < InvalidOperationException , ReadOnlySpan < char > > ( state => writer . WriteStringValueSegment ( state , true ) , secondSegment ) ;
187
+
188
+ stream . Position = 0 ;
189
+ writer . Reset ( ) ;
190
+
191
+ // Base64 + UTF-8
192
+ writer . WriteBase64StringSegment ( firstSegment , false ) ;
193
+ Assert . Throws < InvalidOperationException , ReadOnlySpan < byte > > ( state => writer . WriteStringValueSegment ( state , true ) , MemoryMarshal . AsBytes ( secondSegment ) ) ;
146
194
}
147
195
}
148
196
149
197
ArrayPool < byte > . Shared . Return ( expectedBuffer ) ;
150
198
}
151
199
152
- private static void WriteStringValueSegments ( Utf8JsonWriter writer , ReadOnlySpan < byte > bytes , ReadOnlySpan < Range > ranges )
200
+ private static void WriteStringValueSegments ( Utf8JsonWriter writer , ReadOnlySpan < byte > bytes , ReadOnlySpan < Range > ranges , bool base64Encode )
153
201
{
154
- for ( int i = 0 ; i < ranges . Length ; i ++ )
202
+ if ( base64Encode )
203
+ {
204
+ for ( int i = 0 ; i < ranges . Length ; i ++ )
205
+ {
206
+ writer . WriteBase64StringSegment ( bytes [ ranges [ i ] ] , i == ranges . Length - 1 ) ;
207
+ }
208
+ }
209
+ else
155
210
{
156
- writer . WriteStringValueSegment ( bytes [ ranges [ i ] ] , i == ranges . Length - 1 ) ;
211
+ for ( int i = 0 ; i < ranges . Length ; i ++ )
212
+ {
213
+ writer . WriteStringValueSegment ( bytes [ ranges [ i ] ] , i == ranges . Length - 1 ) ;
214
+ }
157
215
}
158
216
}
159
217
@@ -165,10 +223,20 @@ private static void WriteStringValueSegments(Utf8JsonWriter writer, ReadOnlySpan
165
223
}
166
224
}
167
225
168
- private static int EncodeToUtf8 ( ReadOnlySpan < byte > bytes , Span < byte > destBuffer , JavaScriptEncoder encoder )
226
+ private static int EncodeToUtf8 ( ReadOnlySpan < byte > bytes , Span < byte > destBuffer , JavaScriptEncoder encoder , bool base64Encode )
169
227
{
170
228
destBuffer [ 0 ] = ( byte ) '"' ;
171
- encoder . EncodeUtf8 ( bytes , destBuffer [ 1 ..] , out _ , out int written , isFinalBlock : true ) ;
229
+
230
+ int written ;
231
+ if ( base64Encode )
232
+ {
233
+ Base64 . EncodeToUtf8 ( bytes , destBuffer [ 1 ..] , out _ , out written , isFinalBlock : true ) ;
234
+ }
235
+ else
236
+ {
237
+ encoder . EncodeUtf8 ( bytes , destBuffer [ 1 ..] , out _ , out written , isFinalBlock : true ) ;
238
+ }
239
+
172
240
destBuffer [ ++ written ] = ( byte ) '"' ;
173
241
return written + 1 ;
174
242
}
@@ -181,27 +249,6 @@ private static int EncodeToUtf8(ReadOnlySpan<char> chars, Span<byte> destBuffer,
181
249
return written + 1 ;
182
250
}
183
251
184
- private static int EncodeToUtf8 ( ReadOnlySpan < byte > bytes , ReadOnlySpan < char > chars , Span < byte > destBuffer , JavaScriptEncoder encoder )
185
- {
186
- int written = 1 ;
187
- destBuffer [ 0 ] = ( byte ) '"' ;
188
- encoder . EncodeUtf8 ( bytes , destBuffer [ 1 ..] , out _ , out int writtenTemp , isFinalBlock : true ) ;
189
- written += writtenTemp ;
190
- destBuffer [ written += EncodeTranscode ( chars , destBuffer [ written ..] , encoder , isFinalBlock : true ) ] = ( byte ) '"' ;
191
- return written + 1 ;
192
- }
193
-
194
- private static int EncodeToUtf8 ( ReadOnlySpan < char > chars , ReadOnlySpan < byte > bytes , Span < byte > destBuffer , JavaScriptEncoder encoder )
195
- {
196
- int written = 1 ;
197
- destBuffer [ 0 ] = ( byte ) '"' ;
198
- written += EncodeTranscode ( chars , destBuffer [ 1 ..] , encoder , isFinalBlock : true ) ;
199
- encoder . EncodeUtf8 ( bytes , destBuffer [ written ..] , out _ , out int writtenTemp , isFinalBlock : true ) ;
200
- written += writtenTemp ;
201
- destBuffer [ written ] = ( byte ) '"' ;
202
- return written + 1 ;
203
- }
204
-
205
252
private static int EncodeTranscode ( ReadOnlySpan < char > chars , Span < byte > destBuffer , JavaScriptEncoder encoder , bool isFinalBlock = true )
206
253
{
207
254
var utf16buffer = ArrayPool < char > . Shared . Rent ( 6 * chars . Length ) ;
0 commit comments