| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Buffers; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Diagnostics.CodeAnalysis; |
| | | 7 | | using System.Runtime.InteropServices; |
| | | 8 | | |
| | | 9 | | namespace System.Text |
| | | 10 | | { |
| | | 11 | | // A Decoder is used to decode a sequence of blocks of bytes into a |
| | | 12 | | // sequence of blocks of characters. Following instantiation of a decoder, |
| | | 13 | | // sequential blocks of bytes are converted into blocks of characters through |
| | | 14 | | // calls to the GetChars method. The decoder maintains state between the |
| | | 15 | | // conversions, allowing it to correctly decode byte sequences that span |
| | | 16 | | // adjacent blocks. |
| | | 17 | | // |
| | | 18 | | // Instances of specific implementations of the Decoder abstract base |
| | | 19 | | // class are typically obtained through calls to the GetDecoder method |
| | | 20 | | // of Encoding objects. |
| | | 21 | | |
| | | 22 | | internal class DecoderNLS : Decoder |
| | | 23 | | { |
| | | 24 | | // Remember our encoding |
| | | 25 | | private readonly Encoding _encoding; |
| | | 26 | | private bool _mustFlush; |
| | | 27 | | internal bool _throwOnOverflow; |
| | | 28 | | internal int _bytesUsed; |
| | | 29 | | private int _leftoverBytes; // leftover data from a previous invocation of GetChars (up to 4 bytes) |
| | | 30 | | private int _leftoverByteCount; // number of bytes of actual data in _leftoverBytes |
| | | 31 | | |
| | 144308 | 32 | | internal DecoderNLS(Encoding encoding) |
| | | 33 | | { |
| | 144308 | 34 | | _encoding = encoding; |
| | 144308 | 35 | | _fallback = this._encoding.DecoderFallback; |
| | 144308 | 36 | | this.Reset(); |
| | 144308 | 37 | | } |
| | | 38 | | |
| | | 39 | | public override void Reset() |
| | | 40 | | { |
| | 236995 | 41 | | ClearLeftoverData(); |
| | 236995 | 42 | | _fallbackBuffer?.Reset(); |
| | 47117 | 43 | | } |
| | | 44 | | |
| | | 45 | | public override int GetCharCount(byte[] bytes, int index, int count) |
| | | 46 | | { |
| | 0 | 47 | | return GetCharCount(bytes, index, count, false); |
| | | 48 | | } |
| | | 49 | | |
| | | 50 | | public override unsafe int GetCharCount(byte[] bytes, int index, int count, bool flush) |
| | | 51 | | { |
| | 0 | 52 | | ArgumentNullException.ThrowIfNull(bytes); |
| | | 53 | | |
| | 0 | 54 | | ArgumentOutOfRangeException.ThrowIfNegative(index); |
| | 0 | 55 | | ArgumentOutOfRangeException.ThrowIfNegative(count); |
| | | 56 | | |
| | 0 | 57 | | if (bytes.Length - index < count) |
| | 0 | 58 | | throw new ArgumentOutOfRangeException(nameof(bytes), |
| | 0 | 59 | | SR.ArgumentOutOfRange_IndexCountBuffer); |
| | | 60 | | |
| | | 61 | | // Just call pointer version |
| | 0 | 62 | | fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes)) |
| | 0 | 63 | | return GetCharCount(pBytes + index, count, flush); |
| | | 64 | | } |
| | | 65 | | |
| | | 66 | | public override unsafe int GetCharCount(byte* bytes, int count, bool flush) |
| | | 67 | | { |
| | 144308 | 68 | | ArgumentNullException.ThrowIfNull(bytes); |
| | | 69 | | |
| | 144308 | 70 | | ArgumentOutOfRangeException.ThrowIfNegative(count); |
| | | 71 | | |
| | | 72 | | // Remember the flush |
| | 144308 | 73 | | _mustFlush = flush; |
| | 144308 | 74 | | _throwOnOverflow = true; |
| | | 75 | | |
| | | 76 | | // By default just call the encoding version, no flush by default |
| | 144308 | 77 | | Debug.Assert(_encoding is not null); |
| | 144308 | 78 | | return _encoding.GetCharCount(bytes, count, this); |
| | | 79 | | } |
| | | 80 | | |
| | | 81 | | public override int GetChars(byte[] bytes, int byteIndex, int byteCount, |
| | | 82 | | char[] chars, int charIndex) |
| | | 83 | | { |
| | 0 | 84 | | return GetChars(bytes, byteIndex, byteCount, chars, charIndex, false); |
| | | 85 | | } |
| | | 86 | | |
| | | 87 | | public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount, |
| | | 88 | | char[] chars, int charIndex, bool flush) |
| | | 89 | | { |
| | 0 | 90 | | ArgumentNullException.ThrowIfNull(bytes); |
| | 0 | 91 | | ArgumentNullException.ThrowIfNull(chars); |
| | | 92 | | |
| | 0 | 93 | | ArgumentOutOfRangeException.ThrowIfNegative(byteIndex); |
| | 0 | 94 | | ArgumentOutOfRangeException.ThrowIfNegative(byteCount); |
| | | 95 | | |
| | 0 | 96 | | if (bytes.Length - byteIndex < byteCount) |
| | 0 | 97 | | throw new ArgumentOutOfRangeException(nameof(bytes), |
| | 0 | 98 | | SR.ArgumentOutOfRange_IndexCountBuffer); |
| | | 99 | | |
| | 0 | 100 | | if (charIndex < 0 || charIndex > chars.Length) |
| | 0 | 101 | | throw new ArgumentOutOfRangeException(nameof(charIndex), |
| | 0 | 102 | | SR.ArgumentOutOfRange_IndexMustBeLessOrEqual); |
| | | 103 | | |
| | 0 | 104 | | int charCount = chars.Length - charIndex; |
| | | 105 | | |
| | | 106 | | // Just call pointer version |
| | 0 | 107 | | fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes)) |
| | 0 | 108 | | fixed (char* pChars = &MemoryMarshal.GetArrayDataReference(chars)) |
| | | 109 | | { |
| | | 110 | | // Remember that charCount is # to decode, not size of array |
| | 0 | 111 | | return GetChars(pBytes + byteIndex, byteCount, |
| | 0 | 112 | | pChars + charIndex, charCount, flush); |
| | | 113 | | } |
| | | 114 | | } |
| | | 115 | | |
| | | 116 | | public override unsafe int GetChars(byte* bytes, int byteCount, |
| | | 117 | | char* chars, int charCount, bool flush) |
| | | 118 | | { |
| | 156013 | 119 | | ArgumentNullException.ThrowIfNull(bytes); |
| | 156013 | 120 | | ArgumentNullException.ThrowIfNull(chars); |
| | | 121 | | |
| | 156013 | 122 | | ArgumentOutOfRangeException.ThrowIfNegative(byteCount); |
| | 156013 | 123 | | ArgumentOutOfRangeException.ThrowIfNegative(charCount); |
| | | 124 | | |
| | | 125 | | // Remember our flush |
| | 156013 | 126 | | _mustFlush = flush; |
| | 156013 | 127 | | _throwOnOverflow = true; |
| | | 128 | | |
| | | 129 | | // By default just call the encodings version |
| | 156013 | 130 | | Debug.Assert(_encoding is not null); |
| | 156013 | 131 | | return _encoding.GetChars(bytes, byteCount, chars, charCount, this); |
| | | 132 | | } |
| | | 133 | | |
| | | 134 | | // This method is used when the output buffer might not be big enough. |
| | | 135 | | // Just call the pointer version. (This gets chars) |
| | | 136 | | public override unsafe void Convert(byte[] bytes, int byteIndex, int byteCount, |
| | | 137 | | char[] chars, int charIndex, int charCount, bool flush, |
| | | 138 | | out int bytesUsed, out int charsUsed, out bool completed) |
| | | 139 | | { |
| | 0 | 140 | | ArgumentNullException.ThrowIfNull(bytes); |
| | 0 | 141 | | ArgumentNullException.ThrowIfNull(chars); |
| | | 142 | | |
| | 0 | 143 | | ArgumentOutOfRangeException.ThrowIfNegative(byteIndex); |
| | 0 | 144 | | ArgumentOutOfRangeException.ThrowIfNegative(byteCount); |
| | | 145 | | |
| | 0 | 146 | | ArgumentOutOfRangeException.ThrowIfNegative(charIndex); |
| | 0 | 147 | | ArgumentOutOfRangeException.ThrowIfNegative(charCount); |
| | | 148 | | |
| | 0 | 149 | | if (bytes.Length - byteIndex < byteCount) |
| | 0 | 150 | | throw new ArgumentOutOfRangeException(nameof(bytes), |
| | 0 | 151 | | SR.ArgumentOutOfRange_IndexCountBuffer); |
| | | 152 | | |
| | 0 | 153 | | if (chars.Length - charIndex < charCount) |
| | 0 | 154 | | throw new ArgumentOutOfRangeException(nameof(chars), |
| | 0 | 155 | | SR.ArgumentOutOfRange_IndexCountBuffer); |
| | | 156 | | |
| | | 157 | | // Just call the pointer version (public overrides can't do this) |
| | 0 | 158 | | fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes)) |
| | 0 | 159 | | fixed (char* pChars = &MemoryMarshal.GetArrayDataReference(chars)) |
| | | 160 | | { |
| | 0 | 161 | | Convert(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, flush, |
| | 0 | 162 | | out bytesUsed, out charsUsed, out completed); |
| | | 163 | | } |
| | 0 | 164 | | } |
| | | 165 | | |
| | | 166 | | // This is the version that used pointers. We call the base encoding worker function |
| | | 167 | | // after setting our appropriate internal variables. This is getting chars |
| | | 168 | | public override unsafe void Convert(byte* bytes, int byteCount, |
| | | 169 | | char* chars, int charCount, bool flush, |
| | | 170 | | out int bytesUsed, out int charsUsed, out bool completed) |
| | | 171 | | { |
| | 3105240 | 172 | | ArgumentNullException.ThrowIfNull(bytes); |
| | 3105240 | 173 | | ArgumentNullException.ThrowIfNull(chars); |
| | | 174 | | |
| | 3105240 | 175 | | ArgumentOutOfRangeException.ThrowIfNegative(byteCount); |
| | 3105240 | 176 | | ArgumentOutOfRangeException.ThrowIfNegative(charCount); |
| | | 177 | | |
| | | 178 | | // We don't want to throw |
| | 3105240 | 179 | | _mustFlush = flush; |
| | 3105240 | 180 | | _throwOnOverflow = false; |
| | 3105240 | 181 | | _bytesUsed = 0; |
| | | 182 | | |
| | | 183 | | // Do conversion |
| | 3105240 | 184 | | Debug.Assert(_encoding is not null); |
| | 3105240 | 185 | | charsUsed = _encoding.GetChars(bytes, byteCount, chars, charCount, this); |
| | 3105240 | 186 | | bytesUsed = _bytesUsed; |
| | | 187 | | |
| | | 188 | | // See comment in EncoderNLS.Convert for the details of the logic below. |
| | | 189 | | |
| | 3105240 | 190 | | completed = (bytesUsed == byteCount) |
| | 3105240 | 191 | | && (!flush || !this.HasState) |
| | 3105240 | 192 | | && (_fallbackBuffer is null || _fallbackBuffer.Remaining == 0); |
| | 3105240 | 193 | | } |
| | | 194 | | |
| | 2292097 | 195 | | public bool MustFlush => _mustFlush; |
| | | 196 | | |
| | | 197 | | // Anything left in our decoder? |
| | 67311 | 198 | | internal virtual bool HasState => _leftoverByteCount != 0; |
| | | 199 | | |
| | | 200 | | // Allow encoding to clear our must flush instead of throwing (in ThrowCharsOverflow) |
| | | 201 | | internal void ClearMustFlush() |
| | | 202 | | { |
| | 0 | 203 | | _mustFlush = false; |
| | 0 | 204 | | } |
| | | 205 | | |
| | | 206 | | internal ReadOnlySpan<byte> GetLeftoverData() => |
| | 187785 | 207 | | MemoryMarshal.AsBytes(new ReadOnlySpan<int>(in _leftoverBytes)).Slice(0, _leftoverByteCount); |
| | | 208 | | |
| | | 209 | | internal void SetLeftoverData(ReadOnlySpan<byte> bytes) |
| | | 210 | | { |
| | 187785 | 211 | | bytes.CopyTo(MemoryMarshal.AsBytes(new Span<int>(ref _leftoverBytes))); |
| | 187785 | 212 | | _leftoverByteCount = bytes.Length; |
| | 187785 | 213 | | } |
| | | 214 | | |
| | 3069590 | 215 | | internal bool HasLeftoverData => _leftoverByteCount != 0; |
| | | 216 | | |
| | | 217 | | internal void ClearLeftoverData() |
| | | 218 | | { |
| | 412875 | 219 | | _leftoverByteCount = 0; |
| | 412875 | 220 | | } |
| | | 221 | | |
| | | 222 | | internal int DrainLeftoverDataForGetCharCount(ReadOnlySpan<byte> bytes, out int bytesConsumed) |
| | | 223 | | { |
| | | 224 | | // Quick check: we _should not_ have leftover fallback data from a previous invocation, |
| | | 225 | | // as we'd end up consuming any such data and would corrupt whatever Convert call happens |
| | | 226 | | // to be in progress. Unlike EncoderNLS, this is simply a Debug.Assert. No exception is thrown. |
| | | 227 | | |
| | 0 | 228 | | Debug.Assert(_fallbackBuffer is null || _fallbackBuffer.Remaining == 0, "Should have no data remaining in th |
| | 0 | 229 | | Debug.Assert(HasLeftoverData, "Caller shouldn't invoke this routine unless there's leftover data in the deco |
| | | 230 | | |
| | | 231 | | // Copy the existing leftover data plus as many bytes as possible of the new incoming data |
| | | 232 | | // into a temporary concated buffer, then get its char count by decoding it. |
| | | 233 | | |
| | 0 | 234 | | Span<byte> combinedBuffer = [0, 0, 0, 0]; |
| | 0 | 235 | | combinedBuffer = combinedBuffer.Slice(0, ConcatInto(GetLeftoverData(), bytes, combinedBuffer)); |
| | 0 | 236 | | int charCount = 0; |
| | | 237 | | |
| | 0 | 238 | | Debug.Assert(_encoding is not null); |
| | 0 | 239 | | switch (_encoding.DecodeFirstRune(combinedBuffer, out Rune value, out int combinedBufferBytesConsumed)) |
| | | 240 | | { |
| | | 241 | | case OperationStatus.Done: |
| | 0 | 242 | | charCount = value.Utf16SequenceLength; |
| | 0 | 243 | | goto Finish; // successfully transcoded bytes -> chars |
| | | 244 | | |
| | | 245 | | case OperationStatus.NeedMoreData: |
| | 0 | 246 | | if (MustFlush) |
| | | 247 | | { |
| | 0 | 248 | | goto case OperationStatus.InvalidData; // treat as equivalent to bad data |
| | | 249 | | } |
| | | 250 | | else |
| | | 251 | | { |
| | | 252 | | goto Finish; // consumed some bytes, output 0 chars |
| | | 253 | | } |
| | | 254 | | |
| | | 255 | | case OperationStatus.InvalidData: |
| | | 256 | | break; |
| | | 257 | | |
| | | 258 | | default: |
| | 0 | 259 | | Debug.Fail("Unexpected OperationStatus return value."); |
| | | 260 | | break; |
| | | 261 | | } |
| | | 262 | | |
| | | 263 | | // Couldn't decode the buffer. Fallback the buffer instead. See comment in DrainLeftoverDataForGetChars |
| | | 264 | | // for more information on why a negative index is provided. |
| | | 265 | | |
| | 0 | 266 | | if (FallbackBuffer.Fallback(combinedBuffer.Slice(0, combinedBufferBytesConsumed).ToArray(), index: -_leftove |
| | | 267 | | { |
| | 0 | 268 | | charCount = _fallbackBuffer!.DrainRemainingDataForGetCharCount(); |
| | 0 | 269 | | Debug.Assert(charCount >= 0, "Fallback buffer shouldn't have returned a negative char count."); |
| | | 270 | | } |
| | | 271 | | |
| | | 272 | | Finish: |
| | | 273 | | |
| | 0 | 274 | | bytesConsumed = combinedBufferBytesConsumed - _leftoverByteCount; // amount of 'bytes' buffer consumed just |
| | 0 | 275 | | return charCount; |
| | | 276 | | } |
| | | 277 | | |
| | | 278 | | internal int DrainLeftoverDataForGetChars(ReadOnlySpan<byte> bytes, Span<char> chars, out int bytesConsumed) |
| | | 279 | | { |
| | | 280 | | // Quick check: we _should not_ have leftover fallback data from a previous invocation, |
| | | 281 | | // as we'd end up consuming any such data and would corrupt whatever Convert call happens |
| | | 282 | | // to be in progress. Unlike EncoderNLS, this is simply a Debug.Assert. No exception is thrown. |
| | | 283 | | |
| | 187785 | 284 | | Debug.Assert(_fallbackBuffer is null || _fallbackBuffer.Remaining == 0, "Should have no data remaining in th |
| | 187785 | 285 | | Debug.Assert(HasLeftoverData, "Caller shouldn't invoke this routine unless there's leftover data in the deco |
| | | 286 | | |
| | | 287 | | // Copy the existing leftover data plus as many bytes as possible of the new incoming data |
| | | 288 | | // into a temporary concated buffer, then transcode it from bytes to chars. |
| | | 289 | | |
| | 187785 | 290 | | Span<byte> combinedBuffer = [0, 0, 0, 0]; |
| | 187785 | 291 | | combinedBuffer = combinedBuffer.Slice(0, ConcatInto(GetLeftoverData(), bytes, combinedBuffer)); |
| | 187785 | 292 | | int charsWritten = 0; |
| | | 293 | | |
| | 187785 | 294 | | bool persistNewCombinedBuffer = false; |
| | | 295 | | |
| | 187785 | 296 | | Debug.Assert(_encoding is not null); |
| | 187785 | 297 | | switch (_encoding.DecodeFirstRune(combinedBuffer, out Rune value, out int combinedBufferBytesConsumed)) |
| | | 298 | | { |
| | | 299 | | case OperationStatus.Done: |
| | 54848 | 300 | | if (value.TryEncodeToUtf16(chars, out charsWritten)) |
| | | 301 | | { |
| | 54848 | 302 | | goto Finish; // successfully transcoded bytes -> chars |
| | | 303 | | } |
| | | 304 | | else |
| | | 305 | | { |
| | | 306 | | goto DestinationTooSmall; |
| | | 307 | | } |
| | | 308 | | |
| | | 309 | | case OperationStatus.NeedMoreData: |
| | 12208 | 310 | | if (MustFlush) |
| | | 311 | | { |
| | | 312 | | goto case OperationStatus.InvalidData; // treat as equivalent to bad data |
| | | 313 | | } |
| | | 314 | | else |
| | | 315 | | { |
| | 11905 | 316 | | persistNewCombinedBuffer = true; |
| | 11905 | 317 | | goto Finish; // successfully consumed some bytes, output no chars |
| | | 318 | | } |
| | | 319 | | |
| | | 320 | | case OperationStatus.InvalidData: |
| | | 321 | | break; |
| | | 322 | | |
| | | 323 | | default: |
| | 0 | 324 | | Debug.Fail("Unexpected OperationStatus return value."); |
| | | 325 | | break; |
| | | 326 | | } |
| | | 327 | | |
| | | 328 | | // Couldn't decode the buffer. Fallback the buffer instead. The fallback mechanism relies |
| | | 329 | | // on a negative index to convey "the start of the invalid sequence was some number of |
| | | 330 | | // bytes back before the current buffer." Since we know the invalid sequence must have |
| | | 331 | | // started at the beginning of our leftover byte buffer, we can signal to our caller that |
| | | 332 | | // they must backtrack that many bytes to find the real start of the invalid sequence. |
| | | 333 | | |
| | 121032 | 334 | | if (FallbackBuffer.Fallback(combinedBuffer.Slice(0, combinedBufferBytesConsumed).ToArray(), index: -_leftove |
| | 121032 | 335 | | && !_fallbackBuffer!.TryDrainRemainingDataForGetChars(chars, out charsWritten)) |
| | | 336 | | { |
| | | 337 | | goto DestinationTooSmall; |
| | | 338 | | } |
| | | 339 | | |
| | | 340 | | Finish: |
| | | 341 | | |
| | | 342 | | // Report back the number of bytes (from the new incoming span) we consumed just now. |
| | | 343 | | // This calculation is simple: it's the difference between the original leftover byte |
| | | 344 | | // count and the number of bytes from the combined buffer we needed to decode the first |
| | | 345 | | // scalar value. We need to report this before the call to SetLeftoverData / |
| | | 346 | | // ClearLeftoverData because those methods will overwrite the _leftoverByteCount field. |
| | | 347 | | |
| | 187785 | 348 | | bytesConsumed = combinedBufferBytesConsumed - _leftoverByteCount; |
| | | 349 | | |
| | 187785 | 350 | | if (persistNewCombinedBuffer) |
| | | 351 | | { |
| | 11905 | 352 | | Debug.Assert(combinedBufferBytesConsumed == combinedBuffer.Length, "We should be asked to persist the en |
| | 11905 | 353 | | SetLeftoverData(combinedBuffer); // the buffer still only contains partial data; a future call to Conver |
| | | 354 | | } |
| | | 355 | | else |
| | | 356 | | { |
| | 175880 | 357 | | ClearLeftoverData(); // the buffer contains no partial data; we'll go down the normal paths |
| | | 358 | | } |
| | | 359 | | |
| | 187785 | 360 | | return charsWritten; |
| | | 361 | | |
| | | 362 | | DestinationTooSmall: |
| | | 363 | | |
| | | 364 | | // If we got to this point, we're trying to write chars to the output buffer, but we're unable to do |
| | | 365 | | // so. Unlike EncoderNLS, this type does not allow partial writes to the output buffer. Since we know |
| | | 366 | | // draining leftover data is the first operation performed by any DecoderNLS API, there was no |
| | | 367 | | // opportunity for any code before us to make forward progress, so we must fail immediately. |
| | | 368 | | |
| | 0 | 369 | | _encoding.ThrowCharsOverflow(this, nothingDecoded: true); |
| | 0 | 370 | | throw null!; // will never reach this point |
| | | 371 | | } |
| | | 372 | | |
| | | 373 | | /// <summary> |
| | | 374 | | /// Given a byte buffer <paramref name="dest"/>, concatenates as much of <paramref name="srcLeft"/> followed |
| | | 375 | | /// by <paramref name="srcRight"/> into it as will fit, then returns the total number of bytes copied. |
| | | 376 | | /// </summary> |
| | | 377 | | private static int ConcatInto(ReadOnlySpan<byte> srcLeft, ReadOnlySpan<byte> srcRight, Span<byte> dest) |
| | | 378 | | { |
| | 187785 | 379 | | int total = 0; |
| | | 380 | | |
| | 805666 | 381 | | for (int i = 0; i < srcLeft.Length; i++) |
| | | 382 | | { |
| | 215048 | 383 | | if ((uint)total >= (uint)dest.Length) |
| | | 384 | | { |
| | | 385 | | goto Finish; |
| | | 386 | | } |
| | | 387 | | else |
| | | 388 | | { |
| | 215048 | 389 | | dest[total++] = srcLeft[i]; |
| | | 390 | | } |
| | | 391 | | } |
| | | 392 | | |
| | 1029472 | 393 | | for (int i = 0; i < srcRight.Length; i++) |
| | | 394 | | { |
| | 353813 | 395 | | if ((uint)total >= (uint)dest.Length) |
| | | 396 | | { |
| | | 397 | | goto Finish; |
| | | 398 | | } |
| | | 399 | | else |
| | | 400 | | { |
| | 326951 | 401 | | dest[total++] = srcRight[i]; |
| | | 402 | | } |
| | | 403 | | } |
| | | 404 | | |
| | | 405 | | Finish: |
| | | 406 | | |
| | 187785 | 407 | | return total; |
| | | 408 | | } |
| | | 409 | | } |
| | | 410 | | } |
| | | 411 | | |