| | | 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 | | // See the LICENSE file in the project root for more information. |
| | | 4 | | |
| | | 5 | | using System.Runtime.InteropServices; |
| | | 6 | | |
| | | 7 | | namespace System.Buffers.Text |
| | | 8 | | { |
| | | 9 | | public static partial class Utf8Formatter |
| | | 10 | | { |
| | | 11 | | #region Constants |
| | | 12 | | |
| | | 13 | | private const byte OpenBrace = (byte)'{'; |
| | | 14 | | private const byte CloseBrace = (byte)'}'; |
| | | 15 | | |
| | | 16 | | private const byte OpenParen = (byte)'('; |
| | | 17 | | private const byte CloseParen = (byte)')'; |
| | | 18 | | |
| | | 19 | | private const byte Dash = (byte)'-'; |
| | | 20 | | |
| | | 21 | | #endregion Constants |
| | | 22 | | |
| | | 23 | | /// <summary> |
| | | 24 | | /// Formats a Guid as a UTF8 string. |
| | | 25 | | /// </summary> |
| | | 26 | | /// <param name="value">Value to format</param> |
| | | 27 | | /// <param name="buffer">Buffer to write the UTF8-formatted value to</param> |
| | | 28 | | /// <param name="bytesWritten">Receives the length of the formatted text in bytes</param> |
| | | 29 | | /// <param name="format">The standard format to use</param> |
| | | 30 | | /// <returns> |
| | | 31 | | /// true for success. "bytesWritten" contains the length of the formatted text in bytes. |
| | | 32 | | /// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds. |
| | | 33 | | /// </returns> |
| | | 34 | | /// <remarks> |
| | | 35 | | /// Formats supported: |
| | | 36 | | /// D (default) nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn |
| | | 37 | | /// B {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn} |
| | | 38 | | /// P (nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn) |
| | | 39 | | /// N nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn |
| | | 40 | | /// </remarks> |
| | | 41 | | /// <exceptions> |
| | | 42 | | /// <cref>System.FormatException</cref> if the format is not valid for this data type. |
| | | 43 | | /// </exceptions> |
| | | 44 | | public static bool TryFormat(Guid value, Span<byte> buffer, out int bytesWritten, StandardFormat format = defaul |
| | 1 | 45 | | { |
| | | 46 | | const int INSERT_DASHES = unchecked((int)0x80000000); |
| | | 47 | | const int NO_DASHES = 0; |
| | | 48 | | const int INSERT_CURLY_BRACES = (CloseBrace << 16) | (OpenBrace << 8); |
| | | 49 | | const int INSERT_ROUND_BRACES = (CloseParen << 16) | (OpenParen << 8); |
| | | 50 | | const int NO_BRACES = 0; |
| | | 51 | | const int LEN_GUID_BASE = 32; |
| | | 52 | | const int LEN_ADD_DASHES = 4; |
| | | 53 | | const int LEN_ADD_BRACES = 2; |
| | | 54 | | |
| | | 55 | | // This is a 32-bit value whose contents (where 0 is the low byte) are: |
| | | 56 | | // 0th byte: minimum required length of the output buffer, |
| | | 57 | | // 1st byte: the ASCII byte to insert for the opening brace position (or 0 if no braces), |
| | | 58 | | // 2nd byte: the ASCII byte to insert for the closing brace position (or 0 if no braces), |
| | | 59 | | // 3rd byte: high bit set if dashes are to be inserted. |
| | | 60 | | // |
| | | 61 | | // The reason for keeping a single flag instead of separate vars is that we can avoid register spillage |
| | | 62 | | // as we build up the output value. |
| | | 63 | | int flags; |
| | | 64 | | |
| | 1 | 65 | | switch (FormattingHelpers.GetSymbolOrDefault(format, 'D')) |
| | | 66 | | { |
| | | 67 | | case 'D': // nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn |
| | 1 | 68 | | flags = INSERT_DASHES + NO_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES; |
| | 1 | 69 | | break; |
| | | 70 | | |
| | | 71 | | case 'B': // {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn} |
| | 1 | 72 | | flags = INSERT_DASHES + INSERT_CURLY_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES + LEN_ADD_BRACES; |
| | 1 | 73 | | break; |
| | | 74 | | |
| | | 75 | | case 'P': // (nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn) |
| | 1 | 76 | | flags = INSERT_DASHES + INSERT_ROUND_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES + LEN_ADD_BRACES; |
| | 1 | 77 | | break; |
| | | 78 | | |
| | | 79 | | case 'N': // nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn |
| | 1 | 80 | | flags = NO_BRACES + NO_DASHES + LEN_GUID_BASE; |
| | 1 | 81 | | break; |
| | | 82 | | |
| | | 83 | | default: |
| | 1 | 84 | | return ThrowHelper.TryFormatThrowFormatException(out bytesWritten); |
| | | 85 | | } |
| | | 86 | | |
| | | 87 | | // At this point, the low byte of flags contains the minimum required length |
| | | 88 | | |
| | 1 | 89 | | if ((byte)flags > buffer.Length) |
| | 1 | 90 | | { |
| | 1 | 91 | | bytesWritten = 0; |
| | 1 | 92 | | return false; |
| | | 93 | | } |
| | | 94 | | |
| | 1 | 95 | | bytesWritten = (byte)flags; |
| | 1 | 96 | | flags >>= 8; |
| | | 97 | | |
| | | 98 | | // At this point, the low byte of flags contains the opening brace char (if any) |
| | | 99 | | |
| | 1 | 100 | | if ((byte)flags != 0) |
| | 1 | 101 | | { |
| | 1 | 102 | | buffer[0] = (byte)flags; |
| | 1 | 103 | | buffer = buffer.Slice(1); |
| | 1 | 104 | | } |
| | 1 | 105 | | flags >>= 8; |
| | | 106 | | |
| | | 107 | | // At this point, the low byte of flags contains the closing brace char (if any) |
| | | 108 | | // And since we're performing arithmetic shifting the high bit of flags is set (flags is negative) if dashes |
| | | 109 | | |
| | 1 | 110 | | DecomposedGuid guidAsBytes = default; |
| | 1 | 111 | | guidAsBytes.Guid = value; |
| | | 112 | | |
| | | 113 | | // When a GUID is blitted, the first three components are little-endian, and the last component is big-endia |
| | | 114 | | |
| | | 115 | | // The line below forces the JIT to hoist the bounds check for the following segment. |
| | | 116 | | // The JIT will optimize away the read, but it cannot optimize away the bounds check |
| | | 117 | | // because it may have an observeable side effect (throwing). |
| | | 118 | | // We use 8 instead of 7 so that we also capture the dash if we're asked to insert one. |
| | | 119 | | |
| | 3 | 120 | | { var unused = buffer[8]; } |
| | 1 | 121 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte03, buffer, 0, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 122 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte02, buffer, 2, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 123 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte01, buffer, 4, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 124 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte00, buffer, 6, FormattingHelpers.HexCasing.Lowercase); |
| | | 125 | | |
| | 1 | 126 | | if (flags < 0 /* use dash? */) |
| | 1 | 127 | | { |
| | 1 | 128 | | buffer[8] = Dash; |
| | 1 | 129 | | buffer = buffer.Slice(9); |
| | 1 | 130 | | } |
| | | 131 | | else |
| | 1 | 132 | | { |
| | 1 | 133 | | buffer = buffer.Slice(8); |
| | 1 | 134 | | } |
| | | 135 | | |
| | 3 | 136 | | { var unused = buffer[4]; } |
| | 1 | 137 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte05, buffer, 0, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 138 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte04, buffer, 2, FormattingHelpers.HexCasing.Lowercase); |
| | | 139 | | |
| | 1 | 140 | | if (flags < 0 /* use dash? */) |
| | 1 | 141 | | { |
| | 1 | 142 | | buffer[4] = Dash; |
| | 1 | 143 | | buffer = buffer.Slice(5); |
| | 1 | 144 | | } |
| | | 145 | | else |
| | 1 | 146 | | { |
| | 1 | 147 | | buffer = buffer.Slice(4); |
| | 1 | 148 | | } |
| | | 149 | | |
| | 3 | 150 | | { var unused = buffer[4]; } |
| | 1 | 151 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte07, buffer, 0, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 152 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte06, buffer, 2, FormattingHelpers.HexCasing.Lowercase); |
| | | 153 | | |
| | 1 | 154 | | if (flags < 0 /* use dash? */) |
| | 1 | 155 | | { |
| | 1 | 156 | | buffer[4] = Dash; |
| | 1 | 157 | | buffer = buffer.Slice(5); |
| | 1 | 158 | | } |
| | | 159 | | else |
| | 1 | 160 | | { |
| | 1 | 161 | | buffer = buffer.Slice(4); |
| | 1 | 162 | | } |
| | | 163 | | |
| | 3 | 164 | | { var unused = buffer[4]; } |
| | 1 | 165 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte08, buffer, 0, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 166 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte09, buffer, 2, FormattingHelpers.HexCasing.Lowercase); |
| | | 167 | | |
| | 1 | 168 | | if (flags < 0 /* use dash? */) |
| | 1 | 169 | | { |
| | 1 | 170 | | buffer[4] = Dash; |
| | 1 | 171 | | buffer = buffer.Slice(5); |
| | 1 | 172 | | } |
| | | 173 | | else |
| | 1 | 174 | | { |
| | 1 | 175 | | buffer = buffer.Slice(4); |
| | 1 | 176 | | } |
| | | 177 | | |
| | 3 | 178 | | { var unused = buffer[11]; } // can't hoist bounds check on the final brace (if exists) |
| | 1 | 179 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte10, buffer, 0, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 180 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte11, buffer, 2, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 181 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte12, buffer, 4, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 182 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte13, buffer, 6, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 183 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte14, buffer, 8, FormattingHelpers.HexCasing.Lowercase); |
| | 1 | 184 | | FormattingHelpers.WriteHexByte(guidAsBytes.Byte15, buffer, 10, FormattingHelpers.HexCasing.Lowercase); |
| | | 185 | | |
| | 1 | 186 | | if ((byte)flags != 0) |
| | 1 | 187 | | { |
| | 1 | 188 | | buffer[12] = (byte)flags; |
| | 1 | 189 | | } |
| | | 190 | | |
| | 1 | 191 | | return true; |
| | 1 | 192 | | } |
| | | 193 | | |
| | | 194 | | /// <summary> |
| | | 195 | | /// Used to provide access to the individual bytes of a GUID. |
| | | 196 | | /// </summary> |
| | | 197 | | [StructLayout(LayoutKind.Explicit)] |
| | | 198 | | private struct DecomposedGuid |
| | | 199 | | { |
| | | 200 | | [FieldOffset(00)] public Guid Guid; |
| | | 201 | | [FieldOffset(00)] public byte Byte00; |
| | | 202 | | [FieldOffset(01)] public byte Byte01; |
| | | 203 | | [FieldOffset(02)] public byte Byte02; |
| | | 204 | | [FieldOffset(03)] public byte Byte03; |
| | | 205 | | [FieldOffset(04)] public byte Byte04; |
| | | 206 | | [FieldOffset(05)] public byte Byte05; |
| | | 207 | | [FieldOffset(06)] public byte Byte06; |
| | | 208 | | [FieldOffset(07)] public byte Byte07; |
| | | 209 | | [FieldOffset(08)] public byte Byte08; |
| | | 210 | | [FieldOffset(09)] public byte Byte09; |
| | | 211 | | [FieldOffset(10)] public byte Byte10; |
| | | 212 | | [FieldOffset(11)] public byte Byte11; |
| | | 213 | | [FieldOffset(12)] public byte Byte12; |
| | | 214 | | [FieldOffset(13)] public byte Byte13; |
| | | 215 | | [FieldOffset(14)] public byte Byte14; |
| | | 216 | | [FieldOffset(15)] public byte Byte15; |
| | | 217 | | } |
| | | 218 | | } |
| | | 219 | | } |