[fidl][dart] V2 envelope decoding in Dart

This adds support for decoding wire format V2 tables and unions
in dart.

V2 table tests were run with
Ia51914d2708fa28ffa877862ef4d657ec1cc0505 patched in, which
adds more table tests to the V2 test suite.

Bug: 80880

Change-Id: I59707103c80fc397721b0c23d4793055dfc9a3bb
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/556924
Reviewed-by: Ian McKellar <ianloic@google.com>
Commit-Queue: Benjamin Prosnitz <bprosnitz@google.com>
diff --git a/sdk/dart/fidl/lib/src/error.dart b/sdk/dart/fidl/lib/src/error.dart
index 2f29c81..4895775 100644
--- a/sdk/dart/fidl/lib/src/error.dart
+++ b/sdk/dart/fidl/lib/src/error.dart
@@ -11,6 +11,7 @@
   fidlInvalidPresenceIndicator,
   fidlInvalidNumBytesInEnvelope,
   fidlInvalidNumHandlesInEnvelope,
+  fidlInvalidInlineMarkerInEnvelope,
   fidlTooManyHandles,
   fidlTooFewHandles,
   fidlStringTooLong,
diff --git a/sdk/dart/fidl/lib/src/types.dart b/sdk/dart/fidl/lib/src/types.dart
index 28321af..b8d4791 100644
--- a/sdk/dart/fidl/lib/src/types.dart
+++ b/sdk/dart/fidl/lib/src/types.dart
@@ -940,7 +940,16 @@
   }
 }
 
-const int _kEnvelopeSize = 16;
+int envelopeSize(WireFormat wireFormat) {
+  switch (wireFormat) {
+    case WireFormat.v1:
+      return 16;
+    case WireFormat.v2:
+      return 8;
+    default:
+      throw FidlError('unknown wire format');
+  }
+}
 
 void _encodeEnvelopePresent<T, I extends Iterable<T>>(
     Encoder encoder, int offset, int depth, T field, FidlType<T, I> fieldType) {
@@ -961,33 +970,143 @@
   encoder..encodeUint64(0, offset)..encodeUint64(kAllocAbsent, offset + 8);
 }
 
+enum EnvelopeContentLocation {
+  inline,
+  outOfLine,
+}
+
+enum EnvelopePresence {
+  present,
+  absent,
+}
+
 class EnvelopeHeader {
   int numBytes;
   int numHandles;
-  int fieldPresent;
-  EnvelopeHeader(this.numBytes, this.numHandles, this.fieldPresent);
+  EnvelopePresence presence;
+  EnvelopeContentLocation contentLocation;
+  EnvelopeHeader(
+      this.numBytes, this.numHandles, this.presence, this.contentLocation);
 }
 
 T? _decodeEnvelope<T>(
     Decoder decoder, int offset, int depth, SimpleFidlType<T>? fieldType,
     {bool isEmpty = false}) {
   final header = _decodeEnvelopeHeader(decoder, offset);
-  return _decodeEnvelopeContent(decoder, header, fieldType, depth, isEmpty);
+  return _decodeEnvelopeContent(
+      decoder, header, offset, fieldType, depth, isEmpty);
+}
+
+EnvelopeHeader _decodeV1EnvelopeHeader(Decoder decoder, int offset) {
+  int numBytes = decoder.decodeUint32(offset);
+  int numHandles = decoder.decodeUint32(offset + 4);
+  switch (decoder.decodeUint64(offset + 8)) {
+    case kAllocPresent:
+      if (numBytes % 8 != 0) {
+        throw FidlError('improperly aligned byte count',
+            FidlErrorCode.fidlInvalidNumBytesInEnvelope);
+      }
+      return EnvelopeHeader(
+        numBytes,
+        numHandles,
+        EnvelopePresence.present,
+        EnvelopeContentLocation.outOfLine,
+      );
+    case kAllocAbsent:
+      if (numBytes != 0)
+        throw FidlError('absent envelope with non-zero bytes',
+            FidlErrorCode.fidlInvalidNumBytesInEnvelope);
+      if (numHandles != 0)
+        throw FidlError('absent envelope with non-zero handles',
+            FidlErrorCode.fidlInvalidNumHandlesInEnvelope);
+      return EnvelopeHeader(
+        numBytes,
+        numHandles,
+        EnvelopePresence.absent,
+        EnvelopeContentLocation.outOfLine,
+      );
+    default:
+      throw FidlError(
+          'Bad reference encoding', FidlErrorCode.fidlInvalidPresenceIndicator);
+  }
+}
+
+EnvelopeHeader _decodeV2EnvelopeHeader(Decoder decoder, int offset) {
+  int numHandles = decoder.decodeUint16(offset + 4);
+  switch (decoder.decodeUint16(offset + 6)) {
+    case 0: // out of line content
+      int numBytes = decoder.decodeUint32(offset);
+      if (numBytes % 8 != 0) {
+        throw FidlError('improperly aligned byte count',
+            FidlErrorCode.fidlInvalidNumBytesInEnvelope);
+      }
+      return EnvelopeHeader(
+        numBytes,
+        numHandles,
+        (numBytes != 0 || numHandles != 0)
+            ? EnvelopePresence.present
+            : EnvelopePresence.absent,
+        EnvelopeContentLocation.outOfLine,
+      );
+    case 1: // inlined content
+      return EnvelopeHeader(
+        0,
+        numHandles,
+        EnvelopePresence.present,
+        EnvelopeContentLocation.inline,
+      );
+    default:
+      throw FidlError('invalid inline marker in envelope',
+          FidlErrorCode.fidlInvalidInlineMarkerInEnvelope);
+  }
 }
 
 EnvelopeHeader _decodeEnvelopeHeader(Decoder decoder, int offset) {
-  return EnvelopeHeader(
-    decoder.decodeUint32(offset),
-    decoder.decodeUint32(offset + 4),
-    decoder.decodeUint64(offset + 8),
-  );
+  switch (decoder.wireFormat) {
+    case WireFormat.v1:
+      return _decodeV1EnvelopeHeader(decoder, offset);
+    case WireFormat.v2:
+      return _decodeV2EnvelopeHeader(decoder, offset);
+    default:
+      throw FidlError('unknown wire format');
+  }
 }
 
-T? _decodeEnvelopeContent<T, I extends Iterable<T>>(Decoder decoder,
-    EnvelopeHeader header, FidlType<T, I>? fieldType, int depth, bool isEmpty) {
-  switch (header.fieldPresent) {
-    case kAllocPresent:
+T? _decodeEnvelopeContent<T, I extends Iterable<T>>(
+    Decoder decoder,
+    EnvelopeHeader header,
+    int headerOffset,
+    FidlType<T, I>? fieldType,
+    int depth,
+    bool isEmpty) {
+  switch (header.presence) {
+    case EnvelopePresence.present:
       if (isEmpty) throw FidlError('expected empty envelope');
+
+      if (header.contentLocation == EnvelopeContentLocation.inline) {
+        if (fieldType != null) {
+          final claimedHandles = decoder.countClaimedHandles();
+          final field = fieldType.decode(decoder, headerOffset, depth + 1);
+          final numHandlesConsumed =
+              decoder.countClaimedHandles() - claimedHandles;
+          if (header.numHandles != numHandlesConsumed) {
+            throw FidlError('envelope handles were mis-sized',
+                FidlErrorCode.fidlInvalidNumHandlesInEnvelope);
+          }
+          return field;
+        }
+        for (int i = 0; i < header.numHandles; i++) {
+          final handleInfo = decoder.claimHandle();
+          try {
+            handleInfo.handle.close();
+            // ignore: avoid_catches_without_on_clauses
+          } catch (e) {
+            // best effort
+          }
+        }
+        return null;
+      }
+
       if (fieldType != null) {
         final fieldOffset = decoder.claimMemory(
             fieldType.inlineSize(decoder.wireFormat), depth);
@@ -1019,7 +1138,7 @@
         }
       }
       return null;
-    case kAllocAbsent:
+    case EnvelopePresence.absent:
       if (header.numBytes != 0)
         throw FidlError('absent envelope with non-zero bytes',
             FidlErrorCode.fidlInvalidNumBytesInEnvelope);
@@ -1027,9 +1146,6 @@
         throw FidlError('absent envelope with non-zero handles',
             FidlErrorCode.fidlInvalidNumHandlesInEnvelope);
       return null;
-    default:
-      throw FidlError(
-          'Bad reference encoding', FidlErrorCode.fidlInvalidPresenceIndicator);
   }
 }
 
@@ -1078,7 +1194,8 @@
       ..encodeUint64(kAllocPresent, offset + 8);
 
     // Sizing
-    int envelopeOffset = encoder.alloc(maxOrdinal * _kEnvelopeSize, depth);
+    int envelopeOffset =
+        encoder.alloc(maxOrdinal * envelopeSize(encoder.wireFormat), depth);
 
     // Envelopes, and fields.
     for (int i = 0; i <= maxIndex; i++) {
@@ -1106,7 +1223,7 @@
       } else {
         _encodeEnvelopeAbsent(encoder, envelopeOffset);
       }
-      envelopeOffset += _kEnvelopeSize;
+      envelopeOffset += envelopeSize(encoder.wireFormat);
     }
   }
 
@@ -1132,8 +1249,8 @@
     }
 
     // Offsets.
-    int envelopeOffset =
-        decoder.claimMemory(maxOrdinal * _kEnvelopeSize, depth);
+    int envelopeOffset = decoder.claimMemory(
+        maxOrdinal * envelopeSize(decoder.wireFormat), depth);
 
     // Envelopes, and fields.
     final Map<int, dynamic> argv = {};
@@ -1150,6 +1267,7 @@
       final field = _decodeEnvelopeContent(
           decoder,
           header,
+          envelopeOffset,
           fieldType ??
               UnknownRawDataType(
                   numBytes: header.numBytes, numHandles: header.numHandles),
@@ -1163,7 +1281,7 @@
           unknownData[ordinal] = field;
         }
       }
-      envelopeOffset += _kEnvelopeSize;
+      envelopeOffset += envelopeSize(decoder.wireFormat);
     }
 
     return ctor(argv, unknownData);
@@ -1209,6 +1327,7 @@
       final unknownData = _decodeEnvelopeContent(
           decoder,
           header,
+          envelopeOffset,
           UnknownRawDataType(
               numBytes: header.numBytes, numHandles: header.numHandles),
           depth,
@@ -1224,8 +1343,8 @@
       _maybeThrowOnUnknownHandles(resource, unknownData);
       return ctor(ordinal, unknownData);
     }
-    final field =
-        _decodeEnvelopeContent(decoder, header, fieldType, depth, false);
+    final field = _decodeEnvelopeContent(
+        decoder, header, envelopeOffset, fieldType, depth, false);
     if (field == null) throw FidlError('Bad xunion: missing content');
     return ctor(ordinal, field);
   }
diff --git a/tools/fidl/gidl/dart/conformance.go b/tools/fidl/gidl/dart/conformance.go
index cffa6bc..4f61691 100644
--- a/tools/fidl/gidl/dart/conformance.go
+++ b/tools/fidl/gidl/dart/conformance.go
@@ -171,51 +171,6 @@
 	return buf.Bytes(), err
 }
 
-func containsUnion(decl gidlmixer.Declaration) bool {
-	return containsUnionInner(decl, 0)
-}
-
-func containsUnionInner(decl gidlmixer.Declaration, depth int) bool {
-	if depth > 64 {
-		return false
-	}
-	depth++
-
-	switch decl := decl.(type) {
-	case gidlmixer.PrimitiveDeclaration:
-		return false
-	case *gidlmixer.EnumDecl:
-		return false
-	case *gidlmixer.BitsDecl:
-		return false
-	case *gidlmixer.HandleDecl:
-		return false
-	case *gidlmixer.StringDecl:
-		return false
-	case gidlmixer.ListDeclaration:
-		return containsUnionInner(decl.Elem(), depth)
-	case *gidlmixer.StructDecl:
-		for _, fieldName := range decl.FieldNames() {
-			field, _ := decl.Field(fieldName)
-			if containsUnionInner(field, depth) {
-				return true
-			}
-		}
-		return false
-	case *gidlmixer.TableDecl:
-		for _, fieldName := range decl.FieldNames() {
-			field, _ := decl.Field(fieldName)
-			if containsUnionInner(field, depth) {
-				return true
-			}
-		}
-		return false
-	case *gidlmixer.UnionDecl:
-		return true
-	}
-	panic(fmt.Sprintf("unhandled case %T", decl))
-}
-
 func encodeSuccessCases(gidlEncodeSuccesses []gidlir.EncodeSuccess, schema gidlmixer.Schema) ([]encodeSuccessCase, error) {
 	var encodeSuccessCases []encodeSuccessCase
 	for _, encodeSuccess := range gidlEncodeSuccesses {
@@ -256,12 +211,6 @@
 			if !wireFormatSupportedForDecode(encoding.WireFormat) {
 				continue
 			}
-			if containsUnion(decl) {
-				// Unions are not currently implemented for v2 in dart, but many tests are defined.
-				// Disabling here for simplicity (instead of disabling each gidl test).
-				// TODO(fxbug.dev/80880) Re-enable union tests.
-				continue
-			}
 			decodeSuccessCases = append(decodeSuccessCases, decodeSuccessCase{
 				Name:       testCaseName(decodeSuccess.Name, encoding.WireFormat),
 				WireFormat: wireFormatName(encoding.WireFormat),
@@ -308,7 +257,7 @@
 func decodeFailureCases(gidlDecodeFailures []gidlir.DecodeFailure, schema gidlmixer.Schema) ([]decodeFailureCase, error) {
 	var decodeFailureCases []decodeFailureCase
 	for _, decodeFailure := range gidlDecodeFailures {
-		decl, err := schema.ExtractDeclarationByName(decodeFailure.Type)
+		_, err := schema.ExtractDeclarationByName(decodeFailure.Type)
 		if err != nil {
 			return nil, fmt.Errorf("decode failure %s: %s", decodeFailure.Name, err)
 		}
@@ -321,12 +270,6 @@
 			if !wireFormatSupportedForDecode(encoding.WireFormat) {
 				continue
 			}
-			if containsUnion(decl) {
-				// Unions are not currently implemented for v2 in dart, but many tests are defined.
-				// Disabling here for simplicity (instead of disabling each gidl test).
-				// TODO(fxbug.dev/80880) Re-enable union tests.
-				continue
-			}
 			decodeFailureCases = append(decodeFailureCases, decodeFailureCase{
 				Name:       testCaseName(decodeFailure.Name, encoding.WireFormat),
 				WireFormat: wireFormatName(encoding.WireFormat),
diff --git a/tools/fidl/gidl/goldens/dart_golden.dart.golden b/tools/fidl/gidl/goldens/dart_golden.dart.golden
index 2e7b064..01aa986 100644
--- a/tools/fidl/gidl/goldens/dart_golden.dart.golden
+++ b/tools/fidl/gidl/goldens/dart_golden.dart.golden
@@ -486,6 +486,71 @@
 ]));
 			
 			DecodeSuccessCase.run(
+				'GoldenUnionStruct_v1',
+				fidl.WireFormat.v1,
+				GoldenUnionStruct(v: GoldenUnion.withV(0x1)),
+				kGoldenUnionStruct_Type,
+				Uint8List.fromList([
+0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, //
+0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+]));
+			
+			DecodeSuccessCase.run(
+				'GoldenUnionStruct_v2',
+				fidl.WireFormat.v2,
+				GoldenUnionStruct(v: GoldenUnion.withV(0x1)),
+				kGoldenUnionStruct_Type,
+				Uint8List.fromList([
+0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00, //
+]));
+			
+			DecodeSuccessCase.run(
+				'GoldenNullableUnionStructNonNull_v1',
+				fidl.WireFormat.v1,
+				GoldenNullableUnionStruct(v: GoldenUnion.withV(0x1)),
+				kGoldenNullableUnionStruct_Type,
+				Uint8List.fromList([
+0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, //
+0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+]));
+			
+			DecodeSuccessCase.run(
+				'GoldenNullableUnionStructNonNull_v2',
+				fidl.WireFormat.v2,
+				GoldenNullableUnionStruct(v: GoldenUnion.withV(0x1)),
+				kGoldenNullableUnionStruct_Type,
+				Uint8List.fromList([
+0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00, //
+]));
+			
+			DecodeSuccessCase.run(
+				'GoldenNullableUnionStructNull_v1',
+				fidl.WireFormat.v1,
+				GoldenNullableUnionStruct(v: null),
+				kGoldenNullableUnionStruct_Type,
+				Uint8List.fromList([
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+]));
+			
+			DecodeSuccessCase.run(
+				'GoldenNullableUnionStructNull_v2',
+				fidl.WireFormat.v2,
+				GoldenNullableUnionStruct(v: null),
+				kGoldenNullableUnionStruct_Type,
+				Uint8List.fromList([
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //
+]));
+			
+			DecodeSuccessCase.run(
 				'GoldenByteArrayStruct_v1',
 				fidl.WireFormat.v1,
 				GoldenByteArrayStruct(v: Uint8List.fromList([0x1, 0x2, 0x3, 0x4])),