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 three conditional compilation symbols 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 of 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, or .NET
32// Standard 2.1.
33//
34// Using UNSAFE_BYTEBUFFER and BYTEBUFFER_NO_BOUNDS_CHECK together can yield a
35// performance gain of ~15% for some operations, however doing so is potentially
36// dangerous. Do so at your own risk!
37//
38
39using System;
40using System.Collections.Generic;
41using System.IO;
42using System.Runtime.CompilerServices;
43using System.Runtime.InteropServices;
44using System.Text;
45
46#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
47using System.Buffers.Binary;
48#endif
49
50#if ENABLE_SPAN_T && !UNSAFE_BYTEBUFFER && !NETSTANDARD2_1
51#warning ENABLE_SPAN_T requires UNSAFE_BYTEBUFFER to also be defined
52#endif
53
54namespace Google.FlatBuffers
55{
56 public abstract class ByteBufferAllocator
57 {
58#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
59 public abstract Span<byte> Span { get; }
60 public abstract ReadOnlySpan<byte> ReadOnlySpan { get; }
61 public abstract Memory<byte> Memory { get; }
62 public abstract ReadOnlyMemory<byte> ReadOnlyMemory { get; }
63
64#else
65 public byte[] Buffer
66 {
67 get;
68 protected set;
69 }
70#endif
71
72 public int Length
73 {
74 get;
75 protected set;
76 }
77
78 public abstract void GrowFront(int newSize);
79 }
80
81 public sealed class ByteArrayAllocator : ByteBufferAllocator
82 {
83 private byte[] _buffer;
84
85 public ByteArrayAllocator(byte[] buffer)
86 {
87 _buffer = buffer;
88 InitBuffer();
89 }
90
91 public override void GrowFront(int newSize)
92 {
93 if ((Length & 0xC0000000) != 0)
94 throw new Exception(
95 "ByteBuffer: cannot grow buffer beyond 2 gigabytes.");
96
97 if (newSize < Length)
98 throw new Exception("ByteBuffer: cannot truncate buffer.");
99
100 byte[] newBuffer = new byte[newSize];
101 System.Buffer.BlockCopy(_buffer, 0, newBuffer, newSize - Length, Length);
102 _buffer = newBuffer;
103 InitBuffer();
104 }
105
106#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
107 public override Span<byte> Span => _buffer;
108 public override ReadOnlySpan<byte> ReadOnlySpan => _buffer;
109 public override Memory<byte> Memory => _buffer;
110 public override ReadOnlyMemory<byte> ReadOnlyMemory => _buffer;
111#endif
112
113 private void InitBuffer()
114 {
115 Length = _buffer.Length;
116#if !ENABLE_SPAN_T
117 Buffer = _buffer;
118#endif
119 }
120 }
121
122 /// <summary>
123 /// Class to mimic Java's ByteBuffer which is used heavily in Flatbuffers.
124 /// </summary>
125 public class ByteBuffer
126 {
127 private ByteBufferAllocator _buffer;
128 private int _pos; // Must track start of the buffer.
129
130 public ByteBuffer(ByteBufferAllocator allocator, int position)
131 {
132 _buffer = allocator;
133 _pos = position;
134 }
135
136 public ByteBuffer(int size) : this(new byte[size]) { }
137
138 public ByteBuffer(byte[] buffer) : this(buffer, 0) { }
139
140 public ByteBuffer(byte[] buffer, int pos)
141 {
142 _buffer = new ByteArrayAllocator(buffer);
143 _pos = pos;
144 }
145
146 public int Position
147 {
148 get { return _pos; }
149 set { _pos = value; }
150 }
151
152 public int Length { get { return _buffer.Length; } }
153
154 public void Reset()
155 {
156 _pos = 0;
157 }
158
159 // Create a new ByteBuffer on the same underlying data.
160 // The new ByteBuffer's position will be same as this buffer's.
161 public ByteBuffer Duplicate()
162 {
163 return new ByteBuffer(_buffer, Position);
164 }
165
166 // Increases the size of the ByteBuffer, and copies the old data towards
167 // the end of the new buffer.
168 public void GrowFront(int newSize)
169 {
170 _buffer.GrowFront(newSize);
171 }
172
173 public byte[] ToArray(int pos, int len)
174 {
175 return ToArray<byte>(pos, len);
176 }
177
178 /// <summary>
179 /// A lookup of type sizes. Used instead of Marshal.SizeOf() which has additional
180 /// overhead, but also is compatible with generic functions for simplified code.
181 /// </summary>
182 private static Dictionary<Type, int> genericSizes = new Dictionary<Type, int>()
183 {
184 { typeof(bool), sizeof(bool) },
185 { typeof(float), sizeof(float) },
186 { typeof(double), sizeof(double) },
187 { typeof(sbyte), sizeof(sbyte) },
188 { typeof(byte), sizeof(byte) },
189 { typeof(short), sizeof(short) },
190 { typeof(ushort), sizeof(ushort) },
191 { typeof(int), sizeof(int) },
192 { typeof(uint), sizeof(uint) },
193 { typeof(ulong), sizeof(ulong) },
194 { typeof(long), sizeof(long) },
195 };
196
197 /// <summary>
198 /// Get the wire-size (in bytes) of a type supported by flatbuffers.
199 /// </summary>
200 /// <param name="t">The type to get the wire size of</param>
201 /// <returns></returns>
202 public static int SizeOf<T>()
203 {
204 return genericSizes[typeof(T)];
205 }
206
207 /// <summary>
208 /// Checks if the Type provided is supported as scalar value
209 /// </summary>
210 /// <typeparam name="T">The Type to check</typeparam>
211 /// <returns>True if the type is a scalar type that is supported, falsed otherwise</returns>
212 public static bool IsSupportedType<T>()
213 {
214 return genericSizes.ContainsKey(typeof(T));
215 }
216
217 /// <summary>
218 /// Get the wire-size (in bytes) of an typed array
219 /// </summary>
220 /// <typeparam name="T">The type of the array</typeparam>
221 /// <param name="x">The array to get the size of</param>
222 /// <returns>The number of bytes the array takes on wire</returns>
223 public static int ArraySize<T>(T[] x)
224 {
225 return SizeOf<T>() * x.Length;
226 }
227
228 /// <summary>
229 /// Get the wire-size (in bytes) of an typed array segment, taking only the
230 /// range specified by <paramref name="x"/> into account.
231 /// </summary>
232 /// <typeparam name="T">The type of the array</typeparam>
233 /// <param name="x">The array segment to get the size of</param>
234 /// <returns>The number of bytes the array segment takes on wire</returns>
235 public static int ArraySize<T>(ArraySegment<T> x)
236 {
237 return SizeOf<T>() * x.Count;
238 }
239
240#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
241 public static int ArraySize<T>(Span<T> x)
242 {
243 return SizeOf<T>() * x.Length;
244 }
245#endif
246
247 // Get a portion of the buffer casted into an array of type T, given
248 // the buffer position and length.
249#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
250 public T[] ToArray<T>(int pos, int len)
251 where T : struct
252 {
253 AssertOffsetAndLength(pos, len);
254 return MemoryMarshal.Cast<byte, T>(_buffer.ReadOnlySpan.Slice(pos)).Slice(0, len).ToArray();
255 }
256#else
257 public T[] ToArray<T>(int pos, int len)
258 where T : struct
259 {
260 AssertOffsetAndLength(pos, len);
261 T[] arr = new T[len];
262 Buffer.BlockCopy(_buffer.Buffer, pos, arr, 0, ArraySize(arr));
263 return arr;
264 }
265#endif
266
267 public byte[] ToSizedArray()
268 {
269 return ToArray<byte>(Position, Length - Position);
270 }
271
272 public byte[] ToFullArray()
273 {
274 return ToArray<byte>(0, Length);
275 }
276
277#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
278 public ReadOnlyMemory<byte> ToReadOnlyMemory(int pos, int len)
279 {
280 return _buffer.ReadOnlyMemory.Slice(pos, len);
281 }
282
283 public Memory<byte> ToMemory(int pos, int len)
284 {
285 return _buffer.Memory.Slice(pos, len);
286 }
287
288 public Span<byte> ToSpan(int pos, int len)
289 {
290 return _buffer.Span.Slice(pos, len);
291 }
292#else
293 public ArraySegment<byte> ToArraySegment(int pos, int len)
294 {
295 return new ArraySegment<byte>(_buffer.Buffer, pos, len);
296 }
297
298 public MemoryStream ToMemoryStream(int pos, int len)
299 {
300 return new MemoryStream(_buffer.Buffer, pos, len);
301 }
302#endif
303
304#if !UNSAFE_BYTEBUFFER
305 // A conversion union where all the members are overlapping. This allows to reinterpret the bytes of one type
306 // as another, without additional copies.
307 [StructLayout(LayoutKind.Explicit)]
308 struct ConversionUnion
309 {
310 [FieldOffset(0)] public int intValue;
311 [FieldOffset(0)] public float floatValue;
312 }
313#endif // !UNSAFE_BYTEBUFFER
314
315 // Helper functions for the unsafe version.
316 static public ushort ReverseBytes(ushort input)
317 {
318 return (ushort)(((input & 0x00FFU) << 8) |
319 ((input & 0xFF00U) >> 8));
320 }
321 static public uint ReverseBytes(uint input)
322 {
323 return ((input & 0x000000FFU) << 24) |
324 ((input & 0x0000FF00U) << 8) |
325 ((input & 0x00FF0000U) >> 8) |
326 ((input & 0xFF000000U) >> 24);
327 }
328 static public ulong ReverseBytes(ulong input)
329 {
330 return (((input & 0x00000000000000FFUL) << 56) |
331 ((input & 0x000000000000FF00UL) << 40) |
332 ((input & 0x0000000000FF0000UL) << 24) |
333 ((input & 0x00000000FF000000UL) << 8) |
334 ((input & 0x000000FF00000000UL) >> 8) |
335 ((input & 0x0000FF0000000000UL) >> 24) |
336 ((input & 0x00FF000000000000UL) >> 40) |
337 ((input & 0xFF00000000000000UL) >> 56));
338 }
339
340#if !UNSAFE_BYTEBUFFER && (!ENABLE_SPAN_T || !NETSTANDARD2_1)
341 // Helper functions for the safe (but slower) version.
342 protected void WriteLittleEndian(int offset, int count, ulong data)
343 {
344 if (BitConverter.IsLittleEndian)
345 {
346 for (int i = 0; i < count; i++)
347 {
348 _buffer.Buffer[offset + i] = (byte)(data >> i * 8);
349 }
350 }
351 else
352 {
353 for (int i = 0; i < count; i++)
354 {
355 _buffer.Buffer[offset + count - 1 - i] = (byte)(data >> i * 8);
356 }
357 }
358 }
359
360 protected ulong ReadLittleEndian(int offset, int count)
361 {
362 AssertOffsetAndLength(offset, count);
363 ulong r = 0;
364 if (BitConverter.IsLittleEndian)
365 {
366 for (int i = 0; i < count; i++)
367 {
368 r |= (ulong)_buffer.Buffer[offset + i] << i * 8;
369 }
370 }
371 else
372 {
373 for (int i = 0; i < count; i++)
374 {
375 r |= (ulong)_buffer.Buffer[offset + count - 1 - i] << i * 8;
376 }
377 }
378 return r;
379 }
380#elif ENABLE_SPAN_T && NETSTANDARD2_1
381 protected void WriteLittleEndian(int offset, int count, ulong data)
382 {
383 if (BitConverter.IsLittleEndian)
384 {
385 for (int i = 0; i < count; i++)
386 {
387 _buffer.Span[offset + i] = (byte)(data >> i * 8);
388 }
389 }
390 else
391 {
392 for (int i = 0; i < count; i++)
393 {
394 _buffer.Span[offset + count - 1 - i] = (byte)(data >> i * 8);
395 }
396 }
397 }
398
399 protected ulong ReadLittleEndian(int offset, int count)
400 {
401 AssertOffsetAndLength(offset, count);
402 ulong r = 0;
403 if (BitConverter.IsLittleEndian)
404 {
405 for (int i = 0; i < count; i++)
406 {
407 r |= (ulong)_buffer.Span[offset + i] << i * 8;
408 }
409 }
410 else
411 {
412 for (int i = 0; i < count; i++)
413 {
414 r |= (ulong)_buffer.Span[offset + count - 1 - i] << i * 8;
415 }
416 }
417 return r;
418 }
419#endif
420
421 private void AssertOffsetAndLength(int offset, int length)
422 {
423#if !BYTEBUFFER_NO_BOUNDS_CHECK
424 if (offset < 0 ||
425 offset > _buffer.Length - length)
426 throw new ArgumentOutOfRangeException();
427#endif
428 }
429
430#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
431
432 public void PutSbyte(int offset, sbyte value)
433 {
434 AssertOffsetAndLength(offset, sizeof(sbyte));
435 _buffer.Span[offset] = (byte)value;
436 }
437
438 public void PutByte(int offset, byte value)
439 {
440 AssertOffsetAndLength(offset, sizeof(byte));
441 _buffer.Span[offset] = value;
442 }
443
444 public void PutByte(int offset, byte value, int count)
445 {
446 AssertOffsetAndLength(offset, sizeof(byte) * count);
447 Span<byte> span = _buffer.Span.Slice(offset, count);
448 for (var i = 0; i < span.Length; ++i)
449 span[i] = value;
450 }
451#else
452 public void PutSbyte(int offset, sbyte value)
453 {
454 AssertOffsetAndLength(offset, sizeof(sbyte));
455 _buffer.Buffer[offset] = (byte)value;
456 }
457
458 public void PutByte(int offset, byte value)
459 {
460 AssertOffsetAndLength(offset, sizeof(byte));
461 _buffer.Buffer[offset] = value;
462 }
463
464 public void PutByte(int offset, byte value, int count)
465 {
466 AssertOffsetAndLength(offset, sizeof(byte) * count);
467 for (var i = 0; i < count; ++i)
468 _buffer.Buffer[offset + i] = value;
469 }
470#endif
471
472 // this method exists in order to conform with Java ByteBuffer standards
473 public void Put(int offset, byte value)
474 {
475 PutByte(offset, value);
476 }
477
478#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER
479 public unsafe void PutStringUTF8(int offset, string value)
480 {
481 AssertOffsetAndLength(offset, value.Length);
482 fixed (char* s = value)
483 {
484 fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.Span))
485 {
486 Encoding.UTF8.GetBytes(s, value.Length, buffer + offset, Length - offset);
487 }
488 }
489 }
490#elif ENABLE_SPAN_T && NETSTANDARD2_1
491 public void PutStringUTF8(int offset, string value)
492 {
493 AssertOffsetAndLength(offset, value.Length);
494 Encoding.UTF8.GetBytes(value.AsSpan().Slice(0, value.Length),
495 _buffer.Span.Slice(offset));
496 }
497#else
498 public void PutStringUTF8(int offset, string value)
499 {
500 AssertOffsetAndLength(offset, value.Length);
501 Encoding.UTF8.GetBytes(value, 0, value.Length,
502 _buffer.Buffer, offset);
503 }
504#endif
505
506#if UNSAFE_BYTEBUFFER
507 // Unsafe but more efficient versions of Put*.
508 public void PutShort(int offset, short value)
509 {
510 PutUshort(offset, (ushort)value);
511 }
512
513 public unsafe void PutUshort(int offset, ushort value)
514 {
515 AssertOffsetAndLength(offset, sizeof(ushort));
516#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
517 Span<byte> span = _buffer.Span.Slice(offset);
518 BinaryPrimitives.WriteUInt16LittleEndian(span, value);
519#else
520 fixed (byte* ptr = _buffer.Buffer)
521 {
522 *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian
523 ? value
524 : ReverseBytes(value);
525 }
526#endif
527 }
528
529 public void PutInt(int offset, int value)
530 {
531 PutUint(offset, (uint)value);
532 }
533
534 public unsafe void PutUint(int offset, uint value)
535 {
536 AssertOffsetAndLength(offset, sizeof(uint));
537#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
538 Span<byte> span = _buffer.Span.Slice(offset);
539 BinaryPrimitives.WriteUInt32LittleEndian(span, value);
540#else
541 fixed (byte* ptr = _buffer.Buffer)
542 {
543 *(uint*)(ptr + offset) = BitConverter.IsLittleEndian
544 ? value
545 : ReverseBytes(value);
546 }
547#endif
548 }
549
550 public unsafe void PutLong(int offset, long value)
551 {
552 PutUlong(offset, (ulong)value);
553 }
554
555 public unsafe void PutUlong(int offset, ulong value)
556 {
557 AssertOffsetAndLength(offset, sizeof(ulong));
558#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
559 Span<byte> span = _buffer.Span.Slice(offset);
560 BinaryPrimitives.WriteUInt64LittleEndian(span, value);
561#else
562 fixed (byte* ptr = _buffer.Buffer)
563 {
564 *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian
565 ? value
566 : ReverseBytes(value);
567 }
568#endif
569 }
570
571 public unsafe void PutFloat(int offset, float value)
572 {
573 AssertOffsetAndLength(offset, sizeof(float));
574#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
575 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span))
576#else
577 fixed (byte* ptr = _buffer.Buffer)
578#endif
579 {
580 if (BitConverter.IsLittleEndian)
581 {
582 *(float*)(ptr + offset) = value;
583 }
584 else
585 {
586 *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value));
587 }
588 }
589 }
590
591 public unsafe void PutDouble(int offset, double value)
592 {
593 AssertOffsetAndLength(offset, sizeof(double));
594#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
595 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span))
596#else
597 fixed (byte* ptr = _buffer.Buffer)
598#endif
599 {
600 if (BitConverter.IsLittleEndian)
601 {
602 *(double*)(ptr + offset) = value;
603 }
604 else
605 {
606 *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(&value));
607 }
608 }
609 }
610#else // !UNSAFE_BYTEBUFFER
611 // Slower versions of Put* for when unsafe code is not allowed.
612 public void PutShort(int offset, short value)
613 {
614 AssertOffsetAndLength(offset, sizeof(short));
615 WriteLittleEndian(offset, sizeof(short), (ulong)value);
616 }
617
618 public void PutUshort(int offset, ushort value)
619 {
620 AssertOffsetAndLength(offset, sizeof(ushort));
621 WriteLittleEndian(offset, sizeof(ushort), (ulong)value);
622 }
623
624 public void PutInt(int offset, int value)
625 {
626 AssertOffsetAndLength(offset, sizeof(int));
627 WriteLittleEndian(offset, sizeof(int), (ulong)value);
628 }
629
630 public void PutUint(int offset, uint value)
631 {
632 AssertOffsetAndLength(offset, sizeof(uint));
633 WriteLittleEndian(offset, sizeof(uint), (ulong)value);
634 }
635
636 public void PutLong(int offset, long value)
637 {
638 AssertOffsetAndLength(offset, sizeof(long));
639 WriteLittleEndian(offset, sizeof(long), (ulong)value);
640 }
641
642 public void PutUlong(int offset, ulong value)
643 {
644 AssertOffsetAndLength(offset, sizeof(ulong));
645 WriteLittleEndian(offset, sizeof(ulong), value);
646 }
647
648 public void PutFloat(int offset, float value)
649 {
650 AssertOffsetAndLength(offset, sizeof(float));
651 // TODO(derekbailey): use BitConvert.SingleToInt32Bits() whenever flatbuffers upgrades to a .NET version
652 // that contains it.
653 ConversionUnion union;
654 union.intValue = 0;
655 union.floatValue = value;
656 WriteLittleEndian(offset, sizeof(float), (ulong)union.intValue);
657 }
658
659 public void PutDouble(int offset, double value)
660 {
661 AssertOffsetAndLength(offset, sizeof(double));
662 WriteLittleEndian(offset, sizeof(double), (ulong)BitConverter.DoubleToInt64Bits(value));
663 }
664
665#endif // UNSAFE_BYTEBUFFER
666
667#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
668 public sbyte GetSbyte(int index)
669 {
670 AssertOffsetAndLength(index, sizeof(sbyte));
671 return (sbyte)_buffer.ReadOnlySpan[index];
672 }
673
674 public byte Get(int index)
675 {
676 AssertOffsetAndLength(index, sizeof(byte));
677 return _buffer.ReadOnlySpan[index];
678 }
679#else
680 public sbyte GetSbyte(int index)
681 {
682 AssertOffsetAndLength(index, sizeof(sbyte));
683 return (sbyte)_buffer.Buffer[index];
684 }
685
686 public byte Get(int index)
687 {
688 AssertOffsetAndLength(index, sizeof(byte));
689 return _buffer.Buffer[index];
690 }
691#endif
692
693#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER
694 public unsafe string GetStringUTF8(int startPos, int len)
695 {
696 fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan.Slice(startPos)))
697 {
698 return Encoding.UTF8.GetString(buffer, len);
699 }
700 }
701#elif ENABLE_SPAN_T && NETSTANDARD2_1
702 public string GetStringUTF8(int startPos, int len)
703 {
704 return Encoding.UTF8.GetString(_buffer.Span.Slice(startPos, len));
705 }
706#else
707 public string GetStringUTF8(int startPos, int len)
708 {
709 return Encoding.UTF8.GetString(_buffer.Buffer, startPos, len);
710 }
711#endif
712
713#if UNSAFE_BYTEBUFFER
714 // Unsafe but more efficient versions of Get*.
715 public short GetShort(int offset)
716 {
717 return (short)GetUshort(offset);
718 }
719
720 public unsafe ushort GetUshort(int offset)
721 {
722 AssertOffsetAndLength(offset, sizeof(ushort));
723#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
724 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset);
725 return BinaryPrimitives.ReadUInt16LittleEndian(span);
726#else
727 fixed (byte* ptr = _buffer.Buffer)
728 {
729 return BitConverter.IsLittleEndian
730 ? *(ushort*)(ptr + offset)
731 : ReverseBytes(*(ushort*)(ptr + offset));
732 }
733#endif
734 }
735
736 public int GetInt(int offset)
737 {
738 return (int)GetUint(offset);
739 }
740
741 public unsafe uint GetUint(int offset)
742 {
743 AssertOffsetAndLength(offset, sizeof(uint));
744#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
745 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset);
746 return BinaryPrimitives.ReadUInt32LittleEndian(span);
747#else
748 fixed (byte* ptr = _buffer.Buffer)
749 {
750 return BitConverter.IsLittleEndian
751 ? *(uint*)(ptr + offset)
752 : ReverseBytes(*(uint*)(ptr + offset));
753 }
754#endif
755 }
756
757 public long GetLong(int offset)
758 {
759 return (long)GetUlong(offset);
760 }
761
762 public unsafe ulong GetUlong(int offset)
763 {
764 AssertOffsetAndLength(offset, sizeof(ulong));
765#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
766 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset);
767 return BinaryPrimitives.ReadUInt64LittleEndian(span);
768#else
769 fixed (byte* ptr = _buffer.Buffer)
770 {
771 return BitConverter.IsLittleEndian
772 ? *(ulong*)(ptr + offset)
773 : ReverseBytes(*(ulong*)(ptr + offset));
774 }
775#endif
776 }
777
778 public unsafe float GetFloat(int offset)
779 {
780 AssertOffsetAndLength(offset, sizeof(float));
781#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
782 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan))
783#else
784 fixed (byte* ptr = _buffer.Buffer)
785#endif
786 {
787 if (BitConverter.IsLittleEndian)
788 {
789 return *(float*)(ptr + offset);
790 }
791 else
792 {
793 uint uvalue = ReverseBytes(*(uint*)(ptr + offset));
794 return *(float*)(&uvalue);
795 }
796 }
797 }
798
799 public unsafe double GetDouble(int offset)
800 {
801 AssertOffsetAndLength(offset, sizeof(double));
802#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER
803 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan))
804#else
805 fixed (byte* ptr = _buffer.Buffer)
806#endif
807 {
808 if (BitConverter.IsLittleEndian)
809 {
810 return *(double*)(ptr + offset);
811 }
812 else
813 {
814 ulong uvalue = ReverseBytes(*(ulong*)(ptr + offset));
815 return *(double*)(&uvalue);
816 }
817 }
818 }
819#else // !UNSAFE_BYTEBUFFER
820 // Slower versions of Get* for when unsafe code is not allowed.
821 public short GetShort(int index)
822 {
823 return (short)ReadLittleEndian(index, sizeof(short));
824 }
825
826 public ushort GetUshort(int index)
827 {
828 return (ushort)ReadLittleEndian(index, sizeof(ushort));
829 }
830
831 public int GetInt(int index)
832 {
833 return (int)ReadLittleEndian(index, sizeof(int));
834 }
835
836 public uint GetUint(int index)
837 {
838 return (uint)ReadLittleEndian(index, sizeof(uint));
839 }
840
841 public long GetLong(int index)
842 {
843 return (long)ReadLittleEndian(index, sizeof(long));
844 }
845
846 public ulong GetUlong(int index)
847 {
848 return ReadLittleEndian(index, sizeof(ulong));
849 }
850
851 public float GetFloat(int index)
852 {
853 // TODO(derekbailey): use BitConvert.Int32BitsToSingle() whenever flatbuffers upgrades to a .NET version
854 // that contains it.
855 ConversionUnion union;
856 union.floatValue = 0;
857 union.intValue = (int)ReadLittleEndian(index, sizeof(float));
858 return union.floatValue;
859 }
860
861 public double GetDouble(int index)
862 {
863 return BitConverter.Int64BitsToDouble((long)ReadLittleEndian(index, sizeof(double)));
864 }
865#endif // UNSAFE_BYTEBUFFER
866
867 /// <summary>
868 /// Copies an array of type T into this buffer, ending at the given
869 /// offset into this buffer. The starting offset is calculated based on the length
870 /// of the array and is the value returned.
871 /// </summary>
872 /// <typeparam name="T">The type of the input data (must be a struct)</typeparam>
873 /// <param name="offset">The offset into this buffer where the copy will end</param>
874 /// <param name="x">The array to copy data from</param>
875 /// <returns>The 'start' location of this buffer now, after the copy completed</returns>
876 public int Put<T>(int offset, T[] x)
877 where T : struct
878 {
879 if (x == null)
880 {
881 throw new ArgumentNullException("Cannot put a null array");
882 }
883
884 return Put(offset, new ArraySegment<T>(x));
885 }
886
887 /// <summary>
888 /// Copies an array segment of type T into this buffer, ending at the
889 /// given offset into this buffer. The starting offset is calculated
890 /// based on the count of the array segment and is the value returned.
891 /// </summary>
892 /// <typeparam name="T">The type of the input data (must be a struct)
893 /// </typeparam>
894 /// <param name="offset">The offset into this buffer where the copy
895 /// will end</param>
896 /// <param name="x">The array segment to copy data from</param>
897 /// <returns>The 'start' location of this buffer now, after the copy
898 /// completed</returns>
899 public int Put<T>(int offset, ArraySegment<T> x)
900 where T : struct
901 {
902 if (x.Equals(default(ArraySegment<T>)))
903 {
904 throw new ArgumentNullException("Cannot put a uninitialized array segment");
905 }
906
907 if (x.Count == 0)
908 {
909 throw new ArgumentException("Cannot put an empty array");
910 }
911
912 if (!IsSupportedType<T>())
913 {
914 throw new ArgumentException("Cannot put an array of type "
915 + typeof(T) + " into this buffer");
916 }
917
918 if (BitConverter.IsLittleEndian)
919 {
920 int numBytes = ByteBuffer.ArraySize(x);
921 offset -= numBytes;
922 AssertOffsetAndLength(offset, numBytes);
923 // if we are LE, just do a block copy
924#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
925 MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes));
926#else
927 var srcOffset = ByteBuffer.SizeOf<T>() * x.Offset;
928 Buffer.BlockCopy(x.Array, srcOffset, _buffer.Buffer, offset, numBytes);
929#endif
930 }
931 else
932 {
933 throw new NotImplementedException("Big Endian Support not implemented yet " +
934 "for putting typed arrays");
935 // if we are BE, we have to swap each element by itself
936 //for(int i = x.Length - 1; i >= 0; i--)
937 //{
938 // todo: low priority, but need to genericize the Put<T>() functions
939 //}
940 }
941 return offset;
942 }
943
944 /// <summary>
945 /// Copies an array segment of type T into this buffer, ending at the
946 /// given offset into this buffer. The starting offset is calculated
947 /// based on the count of the array segment and is the value returned.
948 /// </summary>
949 /// <typeparam name="T">The type of the input data (must be a struct)
950 /// </typeparam>
951 /// <param name="offset">The offset into this buffer where the copy
952 /// will end</param>
953 /// <param name="ptr">The pointer to copy data from</param>
954 /// <param name="sizeInBytes">The number of bytes to copy</param>
955 /// <returns>The 'start' location of this buffer now, after the copy
956 /// completed</returns>
957 public int Put<T>(int offset, IntPtr ptr, int sizeInBytes)
958 where T : struct
959 {
960 if (ptr == IntPtr.Zero)
961 {
962 throw new ArgumentNullException("Cannot add a null pointer");
963 }
964
965 if(sizeInBytes <= 0)
966 {
967 throw new ArgumentException("Cannot put an empty array");
968 }
969
970 if (!IsSupportedType<T>())
971 {
972 throw new ArgumentException("Cannot put an array of type "
973 + typeof(T) + " into this buffer");
974 }
975
976 if (BitConverter.IsLittleEndian)
977 {
978 offset -= sizeInBytes;
979 AssertOffsetAndLength(offset, sizeInBytes);
980 // if we are LE, just do a block copy
981#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER
982 unsafe
983 {
984 var span = new Span<byte>(ptr.ToPointer(), sizeInBytes);
985 span.CopyTo(_buffer.Span.Slice(offset, sizeInBytes));
986 }
987#else
988 Marshal.Copy(ptr, _buffer.Buffer, offset, sizeInBytes);
989#endif
990 }
991 else
992 {
993 throw new NotImplementedException("Big Endian Support not implemented yet " +
994 "for putting typed arrays");
995 // if we are BE, we have to swap each element by itself
996 //for(int i = x.Length - 1; i >= 0; i--)
997 //{
998 // todo: low priority, but need to genericize the Put<T>() functions
999 //}
1000 }
1001 return offset;
1002 }
1003
1004#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
1005 public int Put<T>(int offset, Span<T> x)
1006 where T : struct
1007 {
1008 if (x.Length == 0)
1009 {
1010 throw new ArgumentException("Cannot put an empty array");
1011 }
1012
1013 if (!IsSupportedType<T>())
1014 {
1015 throw new ArgumentException("Cannot put an array of type "
1016 + typeof(T) + " into this buffer");
1017 }
1018
1019 if (BitConverter.IsLittleEndian)
1020 {
1021 int numBytes = ByteBuffer.ArraySize(x);
1022 offset -= numBytes;
1023 AssertOffsetAndLength(offset, numBytes);
1024 // if we are LE, just do a block copy
1025 MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes));
1026 }
1027 else
1028 {
1029 throw new NotImplementedException("Big Endian Support not implemented yet " +
1030 "for putting typed arrays");
1031 // if we are BE, we have to swap each element by itself
1032 //for(int i = x.Length - 1; i >= 0; i--)
1033 //{
1034 // todo: low priority, but need to genericize the Put<T>() functions
1035 //}
1036 }
1037 return offset;
1038 }
1039#endif
1040 }
1041}
View as plain text