1 /*
2  * Copyright 2014 Google Inc. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // There are 3 #defines that have an impact on performance / features of this ByteBuffer implementation
18 //
19 //      UNSAFE_BYTEBUFFER
20 //          This will use unsafe code to manipulate the underlying byte array. This
21 //          can yield a reasonable performance increase.
22 //
23 //      BYTEBUFFER_NO_BOUNDS_CHECK
24 //          This will disable the bounds check asserts to the byte array. This can
25 //          yield a small performance gain in normal code..
26 //
27 //      ENABLE_SPAN_T
28 //          This will enable reading and writing blocks of memory with a Span<T> instead if just
29 //          T[].  You can also enable writing directly to shared memory or other types of memory
30 //          by providing a custom implementation of ByteBufferAllocator.
31 //          ENABLE_SPAN_T also requires UNSAFE_BYTEBUFFER to be defined
32 //
33 // Using UNSAFE_BYTEBUFFER and BYTEBUFFER_NO_BOUNDS_CHECK together can yield a
34 // performance gain of ~15% for some operations, however doing so is potentially
35 // dangerous. Do so at your own risk!
36 //
37 
38 using System;
39 using System.Collections.Generic;
40 using System.IO;
41 using System.Runtime.CompilerServices;
42 using System.Runtime.InteropServices;
43 using System.Text;
44 
45 #if ENABLE_SPAN_T
46 using System.Buffers.Binary;
47 #endif
48 
49 #if ENABLE_SPAN_T && !UNSAFE_BYTEBUFFER
50 #error ENABLE_SPAN_T requires UNSAFE_BYTEBUFFER to also be defined
51 #endif
52 
53 namespace FlatBuffers
54 {
55     public abstract class ByteBufferAllocator
56     {
57 #if ENABLE_SPAN_T
58         public abstract Span<byte> Span { get; }
59         public abstract ReadOnlySpan<byte> ReadOnlySpan { get; }
60         public abstract Memory<byte> Memory { get; }
61         public abstract ReadOnlyMemory<byte> ReadOnlyMemory { get; }
62 
63 #else
64         public byte[] Buffer
65         {
66             get;
67             protected set;
68         }
69 #endif
70 
71         public int Length
72         {
73             get;
74             protected set;
75         }
76 
GrowFront(int newSize)77         public abstract void GrowFront(int newSize);
78     }
79 
80     public sealed class ByteArrayAllocator : ByteBufferAllocator
81     {
82         private byte[] _buffer;
83 
ByteArrayAllocator(byte[] buffer)84         public ByteArrayAllocator(byte[] buffer)
85         {
86             _buffer = buffer;
87             InitBuffer();
88         }
89 
GrowFront(int newSize)90         public override void GrowFront(int newSize)
91         {
92             if ((Length & 0xC0000000) != 0)
93                 throw new Exception(
94                     "ByteBuffer: cannot grow buffer beyond 2 gigabytes.");
95 
96             if (newSize < Length)
97                 throw new Exception("ByteBuffer: cannot truncate buffer.");
98 
99             byte[] newBuffer = new byte[newSize];
100             System.Buffer.BlockCopy(_buffer, 0, newBuffer, newSize - Length, Length);
101             _buffer = newBuffer;
102             InitBuffer();
103         }
104 
105 #if ENABLE_SPAN_T
106         public override Span<byte> Span => _buffer;
107         public override ReadOnlySpan<byte> ReadOnlySpan => _buffer;
108         public override Memory<byte> Memory => _buffer;
109         public override ReadOnlyMemory<byte> ReadOnlyMemory => _buffer;
110 #endif
111 
InitBuffer()112         private void InitBuffer()
113         {
114             Length = _buffer.Length;
115 #if !ENABLE_SPAN_T
116             Buffer = _buffer;
117 #endif
118         }
119     }
120 
121     /// <summary>
122     /// Class to mimic Java's ByteBuffer which is used heavily in Flatbuffers.
123     /// </summary>
124     public class ByteBuffer
125     {
126         private ByteBufferAllocator _buffer;
127         private int _pos;  // Must track start of the buffer.
128 
ByteBuffer(ByteBufferAllocator allocator, int position)129         public ByteBuffer(ByteBufferAllocator allocator, int position)
130         {
131             _buffer = allocator;
132             _pos = position;
133         }
134 
ByteBuffer(int size)135         public ByteBuffer(int size) : this(new byte[size]) { }
136 
ByteBuffer(byte[] buffer)137         public ByteBuffer(byte[] buffer) : this(buffer, 0) { }
138 
ByteBuffer(byte[] buffer, int pos)139         public ByteBuffer(byte[] buffer, int pos)
140         {
141             _buffer = new ByteArrayAllocator(buffer);
142             _pos = pos;
143         }
144 
145         public int Position
146         {
147             get { return _pos; }
148             set { _pos = value; }
149         }
150 
151         public int Length { get { return _buffer.Length; } }
152 
Reset()153         public void Reset()
154         {
155             _pos = 0;
156         }
157 
158         // Create a new ByteBuffer on the same underlying data.
159         // The new ByteBuffer's position will be same as this buffer's.
Duplicate()160         public ByteBuffer Duplicate()
161         {
162             return new ByteBuffer(_buffer, Position);
163         }
164 
165         // Increases the size of the ByteBuffer, and copies the old data towards
166         // the end of the new buffer.
GrowFront(int newSize)167         public void GrowFront(int newSize)
168         {
169             _buffer.GrowFront(newSize);
170         }
171 
ToArray(int pos, int len)172         public byte[] ToArray(int pos, int len)
173         {
174             return ToArray<byte>(pos, len);
175         }
176 
177         /// <summary>
178         /// A lookup of type sizes. Used instead of Marshal.SizeOf() which has additional
179         /// overhead, but also is compatible with generic functions for simplified code.
180         /// </summary>
181         private static Dictionary<Type, int> genericSizes = new Dictionary<Type, int>()
182         {
183             { typeof(bool),     sizeof(bool) },
184             { typeof(float),    sizeof(float) },
185             { typeof(double),   sizeof(double) },
186             { typeof(sbyte),    sizeof(sbyte) },
187             { typeof(byte),     sizeof(byte) },
188             { typeof(short),    sizeof(short) },
189             { typeof(ushort),   sizeof(ushort) },
190             { typeof(int),      sizeof(int) },
191             { typeof(uint),     sizeof(uint) },
192             { typeof(ulong),    sizeof(ulong) },
193             { typeof(long),     sizeof(long) },
194         };
195 
196         /// <summary>
197         /// Get the wire-size (in bytes) of a type supported by flatbuffers.
198         /// </summary>
199         /// <param name="t">The type to get the wire size of</param>
200         /// <returns></returns>
SizeOf()201         public static int SizeOf<T>()
202         {
203             return genericSizes[typeof(T)];
204         }
205 
206         /// <summary>
207         /// Checks if the Type provided is supported as scalar value
208         /// </summary>
209         /// <typeparam name="T">The Type to check</typeparam>
210         /// <returns>True if the type is a scalar type that is supported, falsed otherwise</returns>
IsSupportedType()211         public static bool IsSupportedType<T>()
212         {
213             return genericSizes.ContainsKey(typeof(T));
214         }
215 
216         /// <summary>
217         /// Get the wire-size (in bytes) of an typed array
218         /// </summary>
219         /// <typeparam name="T">The type of the array</typeparam>
220         /// <param name="x">The array to get the size of</param>
221         /// <returns>The number of bytes the array takes on wire</returns>
ArraySize(T[] x)222         public static int ArraySize<T>(T[] x)
223         {
224             return SizeOf<T>() * x.Length;
225         }
226 
227 #if ENABLE_SPAN_T
ArraySize(Span<T> x)228         public static int ArraySize<T>(Span<T> x)
229         {
230             return SizeOf<T>() * x.Length;
231         }
232 #endif
233 
234         // Get a portion of the buffer casted into an array of type T, given
235         // the buffer position and length.
236 #if ENABLE_SPAN_T
237         public T[] ToArray<T>(int pos, int len)
238             where T : struct
239         {
AssertOffsetAndLengthFlatBuffers.ByteBuffer.__anon1240             AssertOffsetAndLength(pos, len);
241             return MemoryMarshal.Cast<byte, T>(_buffer.ReadOnlySpan.Slice(pos)).Slice(0, len).ToArray();
242         }
243 #else
244         public T[] ToArray<T>(int pos, int len)
245             where T : struct
246         {
AssertOffsetAndLengthFlatBuffers.ByteBuffer.__anon2247             AssertOffsetAndLength(pos, len);
248             T[] arr = new T[len];
Buffer.BlockCopyFlatBuffers.ByteBuffer.__anon2249             Buffer.BlockCopy(_buffer.Buffer, pos, arr, 0, ArraySize(arr));
250             return arr;
251         }
252 #endif
253 
ToSizedArray()254         public byte[] ToSizedArray()
255         {
256             return ToArray<byte>(Position, Length - Position);
257         }
258 
ToFullArray()259         public byte[] ToFullArray()
260         {
261             return ToArray<byte>(0, Length);
262         }
263 
264 #if ENABLE_SPAN_T
ToReadOnlyMemory(int pos, int len)265         public ReadOnlyMemory<byte> ToReadOnlyMemory(int pos, int len)
266         {
267             return _buffer.ReadOnlyMemory.Slice(pos, len);
268         }
269 
ToMemory(int pos, int len)270         public Memory<byte> ToMemory(int pos, int len)
271         {
272             return _buffer.Memory.Slice(pos, len);
273         }
274 
ToSpan(int pos, int len)275         public Span<byte> ToSpan(int pos, int len)
276         {
277             return _buffer.Span.Slice(pos, len);
278         }
279 #else
ToArraySegment(int pos, int len)280         public ArraySegment<byte> ToArraySegment(int pos, int len)
281         {
282             return new ArraySegment<byte>(_buffer.Buffer, pos, len);
283         }
284 
ToMemoryStream(int pos, int len)285         public MemoryStream ToMemoryStream(int pos, int len)
286         {
287             return new MemoryStream(_buffer.Buffer, pos, len);
288         }
289 #endif
290 
291 #if !UNSAFE_BYTEBUFFER
292         // Pre-allocated helper arrays for convertion.
293         private float[] floathelper = new[] { 0.0f };
294         private int[] inthelper = new[] { 0 };
295         private double[] doublehelper = new[] { 0.0 };
296         private ulong[] ulonghelper = new[] { 0UL };
297 #endif // !UNSAFE_BYTEBUFFER
298 
299         // Helper functions for the unsafe version.
ReverseBytes(ushort input)300         static public ushort ReverseBytes(ushort input)
301         {
302             return (ushort)(((input & 0x00FFU) << 8) |
303                             ((input & 0xFF00U) >> 8));
304         }
ReverseBytes(uint input)305         static public uint ReverseBytes(uint input)
306         {
307             return ((input & 0x000000FFU) << 24) |
308                    ((input & 0x0000FF00U) <<  8) |
309                    ((input & 0x00FF0000U) >>  8) |
310                    ((input & 0xFF000000U) >> 24);
311         }
ReverseBytes(ulong input)312         static public ulong ReverseBytes(ulong input)
313         {
314             return (((input & 0x00000000000000FFUL) << 56) |
315                     ((input & 0x000000000000FF00UL) << 40) |
316                     ((input & 0x0000000000FF0000UL) << 24) |
317                     ((input & 0x00000000FF000000UL) <<  8) |
318                     ((input & 0x000000FF00000000UL) >>  8) |
319                     ((input & 0x0000FF0000000000UL) >> 24) |
320                     ((input & 0x00FF000000000000UL) >> 40) |
321                     ((input & 0xFF00000000000000UL) >> 56));
322         }
323 
324 #if !UNSAFE_BYTEBUFFER
325         // Helper functions for the safe (but slower) version.
WriteLittleEndian(int offset, int count, ulong data)326         protected void WriteLittleEndian(int offset, int count, ulong data)
327         {
328             if (BitConverter.IsLittleEndian)
329             {
330                 for (int i = 0; i < count; i++)
331                 {
332                     _buffer.Buffer[offset + i] = (byte)(data >> i * 8);
333                 }
334             }
335             else
336             {
337                 for (int i = 0; i < count; i++)
338                 {
339                     _buffer.Buffer[offset + count - 1 - i] = (byte)(data >> i * 8);
340                 }
341             }
342         }
343 
ReadLittleEndian(int offset, int count)344         protected ulong ReadLittleEndian(int offset, int count)
345         {
346             AssertOffsetAndLength(offset, count);
347             ulong r = 0;
348             if (BitConverter.IsLittleEndian)
349             {
350                 for (int i = 0; i < count; i++)
351                 {
352                     r |= (ulong)_buffer.Buffer[offset + i] << i * 8;
353                 }
354             }
355             else
356             {
357                 for (int i = 0; i < count; i++)
358                 {
359                     r |= (ulong)_buffer.Buffer[offset + count - 1 - i] << i * 8;
360                 }
361             }
362             return r;
363         }
364 #endif // !UNSAFE_BYTEBUFFER
365 
AssertOffsetAndLength(int offset, int length)366         private void AssertOffsetAndLength(int offset, int length)
367         {
368 #if !BYTEBUFFER_NO_BOUNDS_CHECK
369             if (offset < 0 ||
370                 offset > _buffer.Length - length)
371                 throw new ArgumentOutOfRangeException();
372 #endif
373         }
374 
375 #if ENABLE_SPAN_T
376 
PutSbyte(int offset, sbyte value)377         public void PutSbyte(int offset, sbyte value)
378         {
379             AssertOffsetAndLength(offset, sizeof(sbyte));
380             _buffer.Span[offset] = (byte)value;
381         }
382 
PutByte(int offset, byte value)383         public void PutByte(int offset, byte value)
384         {
385             AssertOffsetAndLength(offset, sizeof(byte));
386             _buffer.Span[offset] = value;
387         }
388 
PutByte(int offset, byte value, int count)389         public void PutByte(int offset, byte value, int count)
390         {
391             AssertOffsetAndLength(offset, sizeof(byte) * count);
392             Span<byte> span = _buffer.Span.Slice(offset, count);
393             for (var i = 0; i < span.Length; ++i)
394                 span[i] = value;
395         }
396 #else
PutSbyte(int offset, sbyte value)397         public void PutSbyte(int offset, sbyte value)
398         {
399             AssertOffsetAndLength(offset, sizeof(sbyte));
400             _buffer.Buffer[offset] = (byte)value;
401         }
402 
PutByte(int offset, byte value)403         public void PutByte(int offset, byte value)
404         {
405             AssertOffsetAndLength(offset, sizeof(byte));
406             _buffer.Buffer[offset] = value;
407         }
408 
PutByte(int offset, byte value, int count)409         public void PutByte(int offset, byte value, int count)
410         {
411             AssertOffsetAndLength(offset, sizeof(byte) * count);
412             for (var i = 0; i < count; ++i)
413                 _buffer.Buffer[offset + i] = value;
414         }
415 #endif
416 
417         // this method exists in order to conform with Java ByteBuffer standards
Put(int offset, byte value)418         public void Put(int offset, byte value)
419         {
420             PutByte(offset, value);
421         }
422 
423 #if ENABLE_SPAN_T
PutStringUTF8(int offset, string value)424         public unsafe void PutStringUTF8(int offset, string value)
425         {
426             AssertOffsetAndLength(offset, value.Length);
427             fixed (char* s = value)
428             {
429                 fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.Span))
430                 {
431                     Encoding.UTF8.GetBytes(s, value.Length, buffer + offset, Length - offset);
432                 }
433             }
434         }
435 #else
PutStringUTF8(int offset, string value)436         public void PutStringUTF8(int offset, string value)
437         {
438             AssertOffsetAndLength(offset, value.Length);
439             Encoding.UTF8.GetBytes(value, 0, value.Length,
440                 _buffer.Buffer, offset);
441         }
442 #endif
443 
444 #if UNSAFE_BYTEBUFFER
445         // Unsafe but more efficient versions of Put*.
PutShort(int offset, short value)446         public void PutShort(int offset, short value)
447         {
448             PutUshort(offset, (ushort)value);
449         }
450 
PutUshort(int offset, ushort value)451         public unsafe void PutUshort(int offset, ushort value)
452         {
453             AssertOffsetAndLength(offset, sizeof(ushort));
454 #if ENABLE_SPAN_T
455             Span<byte> span = _buffer.Span.Slice(offset);
456             BinaryPrimitives.WriteUInt16LittleEndian(span, value);
457 #else
458             fixed (byte* ptr = _buffer.Buffer)
459             {
460                 *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian
461                     ? value
462                     : ReverseBytes(value);
463             }
464 #endif
465         }
466 
PutInt(int offset, int value)467         public void PutInt(int offset, int value)
468         {
469             PutUint(offset, (uint)value);
470         }
471 
PutUint(int offset, uint value)472         public unsafe void PutUint(int offset, uint value)
473         {
474             AssertOffsetAndLength(offset, sizeof(uint));
475 #if ENABLE_SPAN_T
476             Span<byte> span = _buffer.Span.Slice(offset);
477             BinaryPrimitives.WriteUInt32LittleEndian(span, value);
478 #else
479             fixed (byte* ptr = _buffer.Buffer)
480             {
481                 *(uint*)(ptr + offset) = BitConverter.IsLittleEndian
482                     ? value
483                     : ReverseBytes(value);
484             }
485 #endif
486         }
487 
PutLong(int offset, long value)488         public unsafe void PutLong(int offset, long value)
489         {
490             PutUlong(offset, (ulong)value);
491         }
492 
PutUlong(int offset, ulong value)493         public unsafe void PutUlong(int offset, ulong value)
494         {
495             AssertOffsetAndLength(offset, sizeof(ulong));
496 #if ENABLE_SPAN_T
497             Span<byte> span = _buffer.Span.Slice(offset);
498             BinaryPrimitives.WriteUInt64LittleEndian(span, value);
499 #else
500             fixed (byte* ptr = _buffer.Buffer)
501             {
502                 *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian
503                     ? value
504                     : ReverseBytes(value);
505             }
506 #endif
507         }
508 
PutFloat(int offset, float value)509         public unsafe void PutFloat(int offset, float value)
510         {
511             AssertOffsetAndLength(offset, sizeof(float));
512 #if ENABLE_SPAN_T
513             fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span))
514 #else
515             fixed (byte* ptr = _buffer.Buffer)
516 #endif
517             {
518                 if (BitConverter.IsLittleEndian)
519                 {
520                     *(float*)(ptr + offset) = value;
521                 }
522                 else
523                 {
524                     *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value));
525                 }
526             }
527         }
528 
PutDouble(int offset, double value)529         public unsafe void PutDouble(int offset, double value)
530         {
531             AssertOffsetAndLength(offset, sizeof(double));
532 #if ENABLE_SPAN_T
533             fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span))
534 #else
535             fixed (byte* ptr = _buffer.Buffer)
536 #endif
537             {
538                 if (BitConverter.IsLittleEndian)
539                 {
540                     *(double*)(ptr + offset) = value;
541                 }
542                 else
543                 {
544                     *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(&value));
545                 }
546             }
547         }
548 #else // !UNSAFE_BYTEBUFFER
549         // Slower versions of Put* for when unsafe code is not allowed.
PutShort(int offset, short value)550         public void PutShort(int offset, short value)
551         {
552             AssertOffsetAndLength(offset, sizeof(short));
553             WriteLittleEndian(offset, sizeof(short), (ulong)value);
554         }
555 
PutUshort(int offset, ushort value)556         public void PutUshort(int offset, ushort value)
557         {
558             AssertOffsetAndLength(offset, sizeof(ushort));
559             WriteLittleEndian(offset, sizeof(ushort), (ulong)value);
560         }
561 
PutInt(int offset, int value)562         public void PutInt(int offset, int value)
563         {
564             AssertOffsetAndLength(offset, sizeof(int));
565             WriteLittleEndian(offset, sizeof(int), (ulong)value);
566         }
567 
PutUint(int offset, uint value)568         public void PutUint(int offset, uint value)
569         {
570             AssertOffsetAndLength(offset, sizeof(uint));
571             WriteLittleEndian(offset, sizeof(uint), (ulong)value);
572         }
573 
PutLong(int offset, long value)574         public void PutLong(int offset, long value)
575         {
576             AssertOffsetAndLength(offset, sizeof(long));
577             WriteLittleEndian(offset, sizeof(long), (ulong)value);
578         }
579 
PutUlong(int offset, ulong value)580         public void PutUlong(int offset, ulong value)
581         {
582             AssertOffsetAndLength(offset, sizeof(ulong));
583             WriteLittleEndian(offset, sizeof(ulong), value);
584         }
585 
PutFloat(int offset, float value)586         public void PutFloat(int offset, float value)
587         {
588             AssertOffsetAndLength(offset, sizeof(float));
589             floathelper[0] = value;
590             Buffer.BlockCopy(floathelper, 0, inthelper, 0, sizeof(float));
591             WriteLittleEndian(offset, sizeof(float), (ulong)inthelper[0]);
592         }
593 
PutDouble(int offset, double value)594         public void PutDouble(int offset, double value)
595         {
596             AssertOffsetAndLength(offset, sizeof(double));
597             doublehelper[0] = value;
598             Buffer.BlockCopy(doublehelper, 0, ulonghelper, 0, sizeof(double));
599             WriteLittleEndian(offset, sizeof(double), ulonghelper[0]);
600         }
601 
602 #endif // UNSAFE_BYTEBUFFER
603 
604 #if ENABLE_SPAN_T
GetSbyte(int index)605         public sbyte GetSbyte(int index)
606         {
607             AssertOffsetAndLength(index, sizeof(sbyte));
608             return (sbyte)_buffer.ReadOnlySpan[index];
609         }
610 
Get(int index)611         public byte Get(int index)
612         {
613             AssertOffsetAndLength(index, sizeof(byte));
614             return _buffer.ReadOnlySpan[index];
615         }
616 #else
GetSbyte(int index)617         public sbyte GetSbyte(int index)
618         {
619             AssertOffsetAndLength(index, sizeof(sbyte));
620             return (sbyte)_buffer.Buffer[index];
621         }
622 
Get(int index)623         public byte Get(int index)
624         {
625             AssertOffsetAndLength(index, sizeof(byte));
626             return _buffer.Buffer[index];
627         }
628 #endif
629 
630 #if ENABLE_SPAN_T
GetStringUTF8(int startPos, int len)631         public unsafe string GetStringUTF8(int startPos, int len)
632         {
633             fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan.Slice(startPos)))
634             {
635                 return Encoding.UTF8.GetString(buffer, len);
636             }
637         }
638 #else
GetStringUTF8(int startPos, int len)639         public string GetStringUTF8(int startPos, int len)
640         {
641             return Encoding.UTF8.GetString(_buffer.Buffer, startPos, len);
642         }
643 #endif
644 
645 #if UNSAFE_BYTEBUFFER
646         // Unsafe but more efficient versions of Get*.
GetShort(int offset)647         public short GetShort(int offset)
648         {
649             return (short)GetUshort(offset);
650         }
651 
GetUshort(int offset)652         public unsafe ushort GetUshort(int offset)
653         {
654             AssertOffsetAndLength(offset, sizeof(ushort));
655 #if ENABLE_SPAN_T
656             ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset);
657             return BinaryPrimitives.ReadUInt16LittleEndian(span);
658 #else
659             fixed (byte* ptr = _buffer.Buffer)
660             {
661                 return BitConverter.IsLittleEndian
662                     ? *(ushort*)(ptr + offset)
663                     : ReverseBytes(*(ushort*)(ptr + offset));
664             }
665 #endif
666         }
667 
GetInt(int offset)668         public int GetInt(int offset)
669         {
670             return (int)GetUint(offset);
671         }
672 
GetUint(int offset)673         public unsafe uint GetUint(int offset)
674         {
675             AssertOffsetAndLength(offset, sizeof(uint));
676 #if ENABLE_SPAN_T
677             ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset);
678             return BinaryPrimitives.ReadUInt32LittleEndian(span);
679 #else
680             fixed (byte* ptr = _buffer.Buffer)
681             {
682                 return BitConverter.IsLittleEndian
683                     ? *(uint*)(ptr + offset)
684                     : ReverseBytes(*(uint*)(ptr + offset));
685             }
686 #endif
687         }
688 
GetLong(int offset)689         public long GetLong(int offset)
690         {
691             return (long)GetUlong(offset);
692         }
693 
GetUlong(int offset)694         public unsafe ulong GetUlong(int offset)
695         {
696             AssertOffsetAndLength(offset, sizeof(ulong));
697 #if ENABLE_SPAN_T
698             ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset);
699             return BinaryPrimitives.ReadUInt64LittleEndian(span);
700 #else
701             fixed (byte* ptr = _buffer.Buffer)
702             {
703                 return BitConverter.IsLittleEndian
704                     ? *(ulong*)(ptr + offset)
705                     : ReverseBytes(*(ulong*)(ptr + offset));
706             }
707 #endif
708         }
709 
GetFloat(int offset)710         public unsafe float GetFloat(int offset)
711         {
712             AssertOffsetAndLength(offset, sizeof(float));
713 #if ENABLE_SPAN_T
714             fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan))
715 #else
716             fixed (byte* ptr = _buffer.Buffer)
717 #endif
718             {
719                 if (BitConverter.IsLittleEndian)
720                 {
721                     return *(float*)(ptr + offset);
722                 }
723                 else
724                 {
725                     uint uvalue = ReverseBytes(*(uint*)(ptr + offset));
726                     return *(float*)(&uvalue);
727                 }
728             }
729         }
730 
GetDouble(int offset)731         public unsafe double GetDouble(int offset)
732         {
733             AssertOffsetAndLength(offset, sizeof(double));
734 #if ENABLE_SPAN_T
735             fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan))
736 #else
737             fixed (byte* ptr = _buffer.Buffer)
738 #endif
739             {
740                 if (BitConverter.IsLittleEndian)
741                 {
742                     return *(double*)(ptr + offset);
743                 }
744                 else
745                 {
746                     ulong uvalue = ReverseBytes(*(ulong*)(ptr + offset));
747                     return *(double*)(&uvalue);
748                 }
749             }
750         }
751 #else // !UNSAFE_BYTEBUFFER
752         // Slower versions of Get* for when unsafe code is not allowed.
GetShort(int index)753         public short GetShort(int index)
754         {
755             return (short)ReadLittleEndian(index, sizeof(short));
756         }
757 
GetUshort(int index)758         public ushort GetUshort(int index)
759         {
760             return (ushort)ReadLittleEndian(index, sizeof(ushort));
761         }
762 
GetInt(int index)763         public int GetInt(int index)
764         {
765             return (int)ReadLittleEndian(index, sizeof(int));
766         }
767 
GetUint(int index)768         public uint GetUint(int index)
769         {
770             return (uint)ReadLittleEndian(index, sizeof(uint));
771         }
772 
GetLong(int index)773         public long GetLong(int index)
774         {
775             return (long)ReadLittleEndian(index, sizeof(long));
776         }
777 
GetUlong(int index)778         public ulong GetUlong(int index)
779         {
780             return ReadLittleEndian(index, sizeof(ulong));
781         }
782 
GetFloat(int index)783         public float GetFloat(int index)
784         {
785             int i = (int)ReadLittleEndian(index, sizeof(float));
786             inthelper[0] = i;
787             Buffer.BlockCopy(inthelper, 0, floathelper, 0, sizeof(float));
788             return floathelper[0];
789         }
790 
GetDouble(int index)791         public double GetDouble(int index)
792         {
793             ulong i = ReadLittleEndian(index, sizeof(double));
794             // There's Int64BitsToDouble but it uses unsafe code internally.
795             ulonghelper[0] = i;
796             Buffer.BlockCopy(ulonghelper, 0, doublehelper, 0, sizeof(double));
797             return doublehelper[0];
798         }
799 #endif // UNSAFE_BYTEBUFFER
800 
801         /// <summary>
802         /// Copies an array of type T into this buffer, ending at the given
803         /// offset into this buffer. The starting offset is calculated based on the length
804         /// of the array and is the value returned.
805         /// </summary>
806         /// <typeparam name="T">The type of the input data (must be a struct)</typeparam>
807         /// <param name="offset">The offset into this buffer where the copy will end</param>
808         /// <param name="x">The array to copy data from</param>
809         /// <returns>The 'start' location of this buffer now, after the copy completed</returns>
810         public int Put<T>(int offset, T[] x)
811             where T : struct
812         {
813             if (x == null)
814             {
815                 throw new ArgumentNullException("Cannot put a null array");
816             }
817 
818             if (x.Length == 0)
819             {
820                 throw new ArgumentException("Cannot put an empty array");
821             }
822 
823             if (!IsSupportedType<T>())
824             {
825                 throw new ArgumentException("Cannot put an array of type "
826                     + typeof(T) + " into this buffer");
827             }
828 
829             if (BitConverter.IsLittleEndian)
830             {
831                 int numBytes = ByteBuffer.ArraySize(x);
832                 offset -= numBytes;
833                 AssertOffsetAndLength(offset, numBytes);
834                 // if we are LE, just do a block copy
835 #if ENABLE_SPAN_T
836                 MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes));
837 #else
838                 Buffer.BlockCopy(x, 0, _buffer.Buffer, offset, numBytes);
839 #endif
840             }
841             else
842             {
843                 throw new NotImplementedException("Big Endian Support not implemented yet " +
844                     "for putting typed arrays");
845                 // if we are BE, we have to swap each element by itself
846                 //for(int i = x.Length - 1; i >= 0; i--)
847                 //{
848                 //  todo: low priority, but need to genericize the Put<T>() functions
849                 //}
850             }
851             return offset;
852         }
853 
854 #if ENABLE_SPAN_T
855         public int Put<T>(int offset, Span<T> x)
856             where T : struct
857         {
858             if (x.Length == 0)
859             {
860                 throw new ArgumentException("Cannot put an empty array");
861             }
862 
863             if (!IsSupportedType<T>())
864             {
865                 throw new ArgumentException("Cannot put an array of type "
866                     + typeof(T) + " into this buffer");
867             }
868 
869             if (BitConverter.IsLittleEndian)
870             {
871                 int numBytes = ByteBuffer.ArraySize(x);
872                 offset -= numBytes;
873                 AssertOffsetAndLength(offset, numBytes);
874                 // if we are LE, just do a block copy
875                 MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes));
876             }
877             else
878             {
879                 throw new NotImplementedException("Big Endian Support not implemented yet " +
880                     "for putting typed arrays");
881                 // if we are BE, we have to swap each element by itself
882                 //for(int i = x.Length - 1; i >= 0; i--)
883                 //{
884                 //  todo: low priority, but need to genericize the Put<T>() functions
885                 //}
886             }
887             return offset;
888         }
889 #endif
890     }
891 }
892