// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:zircon';

import 'package:litetest/litetest.dart';

/// Helper method to turn a [String] into a [ByteData] containing the
/// text of the string encoded as UTF-8.
ByteData utf8Bytes(final String text) {
  return ByteData.sublistView(utf8.encode(text));
}

// Take from zircon constants in zircon/errors.h, zircon/rights.h, zircon/types.h
abstract class ZX {
  ZX._();

  static const int OK = 0;
  static const int KOID_INVALID = 0;
  static const int ERR_BAD_HANDLE = -11;
  static const int ERR_SHOULD_WAIT = -22;
  static const int ERR_PEER_CLOSED = -24;
  static const int ERR_ACCESS_DENIED = -30;
  static const int EVENTPAIR_PEER_CLOSED = __ZX_OBJECT_PEER_CLOSED;
  static const int CHANNEL_READABLE = __ZX_OBJECT_READABLE;
  static const int CHANNEL_PEER_CLOSED = __ZX_OBJECT_PEER_CLOSED;
  static const int SOCKET_READABLE = __ZX_OBJECT_READABLE;
  static const int SOCKET_PEER_CLOSED = __ZX_OBJECT_PEER_CLOSED;
  static const int RIGHT_DUPLICATE = 1 << 0;
  static const int RIGHT_TRANSFER = 1 << 1;
  static const int RIGHT_READ = 1 << 2;
  static const int RIGHT_WRITE = 1 << 3;
  static const int RIGHT_GET_PROPERTY = 1 << 6;
  static const int RIGHT_SET_PROPERTY = 1 << 7;
  static const int RIGHT_MAP = 1 << 5;
  static const int RIGHT_SIGNAL = 1 << 12;
  static const int RIGHT_WAIT = 1 << 14;
  static const int RIGHT_INSPECT = 1 << 15;
  static const int RIGHT_SAME_RIGHTS = 1 << 31;
  static const int RIGHTS_BASIC = RIGHT_TRANSFER | RIGHT_DUPLICATE |
                                  RIGHT_WAIT | RIGHT_INSPECT;
  static const int RIGHTS_IO = RIGHT_READ | RIGHT_WRITE;
  static const int RIGHTS_PROPERTY = RIGHT_GET_PROPERTY | RIGHT_SET_PROPERTY;
  static const int DEFAULT_VMO_RIGHTS = RIGHTS_BASIC | RIGHTS_IO |
                                        RIGHTS_PROPERTY | RIGHT_MAP |
                                        RIGHT_SIGNAL;
  static const int OBJ_TYPE_VMO = 3;
  static const int OBJ_TYPE_CHANNEL = 4;
  static const int HANDLE_OP_MOVE = 0;
  static const int HANDLE_OP_DUPLICATE = 1;
  static const int __ZX_OBJECT_READABLE = 1 << 0;
  static const int __ZX_OBJECT_PEER_CLOSED = 1 << 2;
}

void main() {
  group('handle', () {
    test('create and duplicate handles', () {
      final HandlePairResult pair = System.eventpairCreate();
      expect(pair.status, equals(ZX.OK));
      expect(pair.first.isValid, isTrue);
      expect(pair.second.isValid, isTrue);

      final Handle duplicate = pair.first.duplicate(ZX.RIGHT_SAME_RIGHTS);
      expect(duplicate.isValid, isTrue);

      final Handle failedDuplicate = pair.first.duplicate(-1);
      expect(failedDuplicate.isValid, isFalse);
    });

    test('failure invalid rights', () {
      final HandleResult vmo = System.vmoCreate(0);
      expect(vmo.status, equals(ZX.OK));
      final Handle failedDuplicate = vmo.handle.duplicate(-1);
      expect(failedDuplicate.isValid, isFalse);
      expect(vmo.handle.isValid, isTrue);
    });

    test('failure invalid handle', () {
      final Handle handle = Handle.invalid();
      final Handle duplicate = handle.duplicate(ZX.RIGHT_SAME_RIGHTS);
      expect(duplicate.isValid, isFalse);
    });

    test('duplicated handle should have same koid', () {
      final HandlePairResult pair = System.eventpairCreate();
      expect(pair.status, equals(ZX.OK));
      expect(pair.first.isValid, isTrue);
      expect(pair.second.isValid, isTrue);

      final Handle duplicate = pair.first.duplicate(ZX.RIGHT_SAME_RIGHTS);
      expect(duplicate.isValid, isTrue);

      expect(pair.first.koid, duplicate.koid);
    });

    // TODO(fxbug.dev/77599): Simplify once zx_object_get_info is available.
    test('reduced rights', () {
      // Set up handle.
      final HandleResult vmo = System.vmoCreate(2);
      expect(vmo.status, equals(ZX.OK));

      // Duplicate the first handle.
      final Handle duplicate = vmo.handle.duplicate(ZX.RIGHTS_BASIC);
      expect(duplicate.isValid, isTrue);

      // Write bytes to the original handle.
      final ByteData data1 = utf8Bytes('a');
      final int status1 = System.vmoWrite(vmo.handle, 0, data1);
      expect(status1, equals(ZX.OK));

      // Write bytes to the duplicated handle.
      final ByteData data2 = utf8Bytes('b');
      final int status2 = System.vmoWrite(duplicate, 1, data2);
      expect(status2, equals(ZX.ERR_ACCESS_DENIED));

      // Read bytes.
      final ReadResult readResult = System.vmoRead(vmo.handle, 0, 2);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals(2));
      expect(readResult.bytes.lengthInBytes, equals(2));
      expect(readResult.bytesAsUTF8String(), equals('a\x00'));
    });

    test('create and replace handles', () {
      final HandlePairResult pair = System.eventpairCreate();
      expect(pair.status, equals(ZX.OK));
      expect(pair.first.isValid, isTrue);
      expect(pair.second.isValid, isTrue);

      final Handle replaced = pair.first.replace(ZX.RIGHT_SAME_RIGHTS);
      expect(replaced.isValid, isTrue);
      expect(pair.first.isValid, isFalse);
    });

    test('failure invalid rights', () {
      final HandleResult vmo = System.vmoCreate(0);
      expect(vmo.status, equals(ZX.OK));
      final Handle failedDuplicate = vmo.handle.replace(-1);
      expect(failedDuplicate.isValid, isFalse);
      expect(vmo.handle.isValid, isFalse);
    });

    test('failure invalid handle', () {
      final Handle handle = Handle.invalid();
      final Handle replaced = handle.replace(ZX.RIGHT_SAME_RIGHTS);
      expect(handle.isValid, isFalse);
      expect(replaced.isValid, isFalse);
    });

    test('transferred handle should have same koid', () {
      final HandlePairResult pair = System.eventpairCreate();
      expect(pair.status, equals(ZX.OK));
      expect(pair.first.isValid, isTrue);
      expect(pair.second.isValid, isTrue);

      final int koid = pair.first.koid;
      final Handle replaced = pair.first.replace(ZX.RIGHT_SAME_RIGHTS);
      expect(replaced.isValid, isTrue);

      expect(koid, replaced.koid);
    });

    // TODO(fxbug.dev/77599): Simplify once zx_object_get_info is available.
    test('reduced rights', () {
      // Set up handle.
      final HandleResult vmo = System.vmoCreate(2);
      expect(vmo.status, equals(ZX.OK));

      // Replace the first handle.
      final Handle duplicate =
          vmo.handle.replace(ZX.RIGHTS_BASIC | ZX.RIGHT_READ);
      expect(duplicate.isValid, isTrue);

      // Write bytes to the original handle.
      final ByteData data1 = utf8Bytes('a');
      final int status1 = System.vmoWrite(vmo.handle, 0, data1);
      expect(status1, equals(ZX.ERR_BAD_HANDLE));

      // Write bytes to the duplicated handle.
      final ByteData data2 = utf8Bytes('b');
      final int status2 = System.vmoWrite(duplicate, 1, data2);
      expect(status2, equals(ZX.ERR_ACCESS_DENIED));

      // Read bytes.
      final ReadResult readResult = System.vmoRead(duplicate, 0, 2);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals(2));
      expect(readResult.bytes.lengthInBytes, equals(2));
      expect(readResult.bytesAsUTF8String(), equals('\x00\x00'));
    });

    test('cache koid and invalidate', () {
      final HandleResult vmo = System.vmoCreate(0);
      expect(vmo.status, equals(ZX.OK));
      int originalKoid = vmo.handle.koid;
      expect(originalKoid, notEquals(ZX.KOID_INVALID));
      // Cached koid should be same value.
      expect(originalKoid, equals(vmo.handle.koid));
      vmo.handle.close();
      // koid should be invalidated.
      expect(vmo.handle.koid, equals(ZX.KOID_INVALID));
    });

    test('store the disposition arguments correctly', () {
      final Handle handle = System.channelCreate().first;
      final HandleDisposition disposition = HandleDisposition(1, handle, 2, 3);
      expect(disposition.operation, equals(1));
      expect(disposition.handle, equals(handle));
      expect(disposition.type, equals(2));
      expect(disposition.rights, equals(3));
      expect(disposition.result, equals(ZX.OK));
    });
  });

  group('channel', () {
    test('create channel', () {
      final HandlePairResult pair = System.channelCreate();
      expect(pair.status, equals(ZX.OK));
      expect(pair.first.isValid, isTrue);
      expect(pair.second.isValid, isTrue);
    });

    test('close channel', () {
      final HandlePairResult pair = System.channelCreate();
      expect(pair.first.close(), equals(0));
      expect(pair.first.isValid, isFalse);
      expect(pair.second.isValid, isTrue);
      expect(System.channelWrite(pair.first, ByteData(1), <Handle>[]),
          equals(ZX.ERR_BAD_HANDLE));
      expect(System.channelWrite(pair.second, ByteData(1), <Handle>[]),
          equals(ZX.ERR_PEER_CLOSED));
    });

    test('channel bytes', () {
      final HandlePairResult pair = System.channelCreate();

      // When no data is available, ZX.ERR_SHOULD_WAIT is returned.
      expect(System.channelQueryAndRead(pair.second).status,
          equals(ZX.ERR_SHOULD_WAIT));

      // Write bytes.
      final ByteData data = utf8Bytes('Hello, world');
      final int status = System.channelWrite(pair.first, data, <Handle>[]);
      expect(status, equals(ZX.OK));

      // Read bytes.
      final ReadResult readResult = System.channelQueryAndRead(pair.second);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals(data.lengthInBytes));
      expect(readResult.bytes.lengthInBytes, equals(data.lengthInBytes));
      expect(readResult.bytesAsUTF8String(), equals('Hello, world'));
      expect(readResult.handles.length, equals(0));
    });

    test('channel handles', () {
      final HandlePairResult pair = System.channelCreate();
      final ByteData data = utf8Bytes('');
      final HandlePairResult eventPair = System.eventpairCreate();
      final int status =
          System.channelWrite(pair.first, data, <Handle>[eventPair.first]);
      expect(status, equals(ZX.OK));
      expect(eventPair.first.isValid, isFalse);

      final ReadResult readResult = System.channelQueryAndRead(pair.second);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals(0));
      expect(readResult.bytes.lengthInBytes, equals(0));
      expect(readResult.bytesAsUTF8String(), equals(''));
      expect(readResult.handles.length, equals(1));
      expect(readResult.handles[0].isValid, isTrue);
    });

    test('async wait channel read', () async {
      final HandlePairResult pair = System.channelCreate();
      final Completer<List<int>> completer = Completer<List<int>>();
      pair.first.asyncWait(ZX.CHANNEL_READABLE, (int status, int pending) {
        completer.complete(<int>[status, pending]);
      });

      expect(completer.isCompleted, isFalse);

      System.channelWrite(pair.second, utf8Bytes('Hi'), <Handle>[]);

      final List<int> result = await completer.future;
      expect(result[0], equals(ZX.OK)); // status
      expect(result[1] & ZX.CHANNEL_READABLE,
          equals(ZX.CHANNEL_READABLE)); // pending
    });

    test('async wait channel closed', () async {
      final HandlePairResult pair = System.channelCreate();
      final Completer<int> completer = Completer<int>();
      pair.first.asyncWait(ZX.CHANNEL_PEER_CLOSED, (int status, int pending) {
        completer.complete(status);
      });

      expect(completer.isCompleted, isFalse);

      pair.second.close();

      final int status = await completer.future;
      expect(status, equals(ZX.OK));
    });
  });

  group('channel etc functions', () {
    test('moved handle', () {
      final HandlePairResult pair = System.channelCreate();
      final ByteData data = utf8Bytes('');
      final HandlePairResult transferred = System.channelCreate();

      final HandleDisposition disposition = HandleDisposition(ZX.HANDLE_OP_MOVE,
          transferred.first, ZX.OBJ_TYPE_CHANNEL, ZX.RIGHTS_IO);
      final int status = System.channelWriteEtc(
          pair.first, data, <HandleDisposition>[disposition]);
      expect(status, equals(ZX.OK));
      expect(disposition.result, equals(ZX.OK));
      expect(transferred.first.isValid, isFalse);

      final ReadEtcResult readResult =
          System.channelQueryAndReadEtc(pair.second);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals(0));
      expect(readResult.bytes.lengthInBytes, equals(0));
      expect(readResult.bytesAsUTF8String(), equals(''));
      expect(readResult.handleInfos.length, equals(1));
      final HandleInfo handleInfo = readResult.handleInfos[0];
      expect(handleInfo.handle.isValid, isTrue);
      expect(handleInfo.type, equals(ZX.OBJ_TYPE_CHANNEL));
      expect(handleInfo.rights, equals(ZX.RIGHTS_IO));
    });

    test('copied handle', () {
      final HandlePairResult pair = System.channelCreate();
      final ByteData data = utf8Bytes('');
      final HandleResult vmo = System.vmoCreate(0);

      final HandleDisposition disposition = HandleDisposition(
          ZX.HANDLE_OP_DUPLICATE,
          vmo.handle,
          ZX.OBJ_TYPE_VMO,
          ZX.RIGHT_SAME_RIGHTS);
      final int status = System.channelWriteEtc(
          pair.first, data, <HandleDisposition>[disposition]);
      expect(status, equals(ZX.OK));
      expect(disposition.result, equals(ZX.OK));
      expect(vmo.handle.isValid, isTrue);

      final ReadEtcResult readResult =
          System.channelQueryAndReadEtc(pair.second);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals(0));
      expect(readResult.bytes.lengthInBytes, equals(0));
      expect(readResult.bytesAsUTF8String(), equals(''));
      expect(readResult.handleInfos.length, equals(1));
      final HandleInfo handleInfo = readResult.handleInfos[0];
      expect(handleInfo.handle.isValid, isTrue);
      expect(handleInfo.type, equals(ZX.OBJ_TYPE_VMO));
      expect(handleInfo.rights, equals(ZX.DEFAULT_VMO_RIGHTS));
    });

    test('closed handle should error', () {
      final HandlePairResult pair = System.channelCreate();
      final ByteData data = utf8Bytes('');
      final HandlePairResult closed = System.channelCreate();

      final HandleDisposition disposition = HandleDisposition(ZX.HANDLE_OP_MOVE,
          closed.first, ZX.OBJ_TYPE_CHANNEL, ZX.RIGHT_SAME_RIGHTS);
      closed.first.close();
      final int status = System.channelWriteEtc(
          pair.first, data, <HandleDisposition>[disposition]);
      expect(status, equals(ZX.ERR_BAD_HANDLE));
      expect(disposition.result, equals(ZX.ERR_BAD_HANDLE));
      expect(closed.first.isValid, isFalse);

      final ReadEtcResult readResult =
          System.channelQueryAndReadEtc(pair.second);
      expect(readResult.status, equals(ZX.ERR_SHOULD_WAIT));
    });

    test('multiple handles', () {
      final HandlePairResult pair = System.channelCreate();
      final ByteData data = utf8Bytes('');
      final HandlePairResult transferred = System.channelCreate();
      final HandleResult vmo = System.vmoCreate(0);

      final List<HandleDisposition> dispositions = [
        HandleDisposition(ZX.HANDLE_OP_MOVE, transferred.first,
            ZX.OBJ_TYPE_CHANNEL, ZX.RIGHTS_IO),
        HandleDisposition(ZX.HANDLE_OP_DUPLICATE, vmo.handle, ZX.OBJ_TYPE_VMO,
            ZX.RIGHT_SAME_RIGHTS)
      ];
      final int status = System.channelWriteEtc(pair.first, data, dispositions);
      expect(status, equals(ZX.OK));
      expect(dispositions[0].result, equals(ZX.OK));
      expect(dispositions[1].result, equals(ZX.OK));
      expect(transferred.first.isValid, isFalse);
      expect(vmo.handle.isValid, isTrue);

      final ReadEtcResult readResult =
          System.channelQueryAndReadEtc(pair.second);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals(0));
      expect(readResult.bytes.lengthInBytes, equals(0));
      expect(readResult.bytesAsUTF8String(), equals(''));

      expect(readResult.handleInfos.length, equals(2));
      final HandleInfo handleInfo = readResult.handleInfos[0];
      expect(handleInfo.handle.isValid, isTrue);
      expect(handleInfo.type, equals(ZX.OBJ_TYPE_CHANNEL));
      expect(handleInfo.rights, equals(ZX.RIGHTS_IO));
      final HandleInfo vmoInfo = readResult.handleInfos[1];
      expect(vmoInfo.handle.isValid, isTrue);
      expect(vmoInfo.type, equals(ZX.OBJ_TYPE_VMO));
      expect(vmoInfo.rights, equals(ZX.DEFAULT_VMO_RIGHTS));
    });
  });

  group('eventpair', () {
    test('create', () {
      final HandlePairResult pair = System.eventpairCreate();
      expect(pair.status, equals(ZX.OK));
      expect(pair.first.isValid, isTrue);
      expect(pair.second.isValid, isTrue);
    });

    test('duplicate', () {
      final HandlePairResult pair = System.eventpairCreate();
      expect(pair.status, equals(ZX.OK));
      expect(pair.first.isValid, isTrue);
      expect(pair.second.isValid, isTrue);

      expect(pair.first.duplicate(ZX.RIGHT_SAME_RIGHTS).isValid, isTrue);
      expect(pair.second.duplicate(ZX.RIGHT_SAME_RIGHTS).isValid, isTrue);
    });

    test('close', () {
      final HandlePairResult pair = System.eventpairCreate();
      expect(pair.first.close(), equals(0));
      expect(pair.first.isValid, isFalse);
      expect(pair.second.isValid, isTrue);

      expect(pair.second.close(), equals(0));
      expect(pair.second.isValid, isFalse);
    });

    test('async wait peer closed', () async {
      final HandlePairResult pair = System.eventpairCreate();
      final Completer<int> completer = Completer<int>();
      pair.first.asyncWait(ZX.EVENTPAIR_PEER_CLOSED, (int status, int pending) {
        completer.complete(status);
      });

      expect(completer.isCompleted, isFalse);
      pair.second.close();

      final int status = await completer.future;
      expect(status, equals(ZX.OK));
    });
  });

  // NOTE: This only tests stream sockets.
  // We should add tests for datagram sockets.
  group('socket', () {
    test('create socket', () {
      final HandlePairResult pair = System.socketCreate();
      expect(pair.status, equals(ZX.OK));
      expect(pair.first.isValid, isTrue);
      expect(pair.second.isValid, isTrue);
    });

    test('close socket', () {
      final HandlePairResult pair = System.socketCreate();
      expect(pair.first.close(), equals(0));
      expect(pair.first.isValid, isFalse);
      expect(pair.second.isValid, isTrue);
      final WriteResult firstResult =
          System.socketWrite(pair.first, ByteData(1), 0);
      expect(firstResult.status, equals(ZX.ERR_BAD_HANDLE));
      final WriteResult secondResult =
          System.socketWrite(pair.second, ByteData(1), 0);
      expect(secondResult.status, equals(ZX.ERR_PEER_CLOSED));
    });

    test('read write socket', () {
      final HandlePairResult pair = System.socketCreate();

      // When no data is available, ZX.ERR_SHOULD_WAIT is returned.
      expect(
          System.socketRead(pair.second, 1).status, equals(ZX.ERR_SHOULD_WAIT));

      final ByteData data = utf8Bytes('Hello, world');
      final WriteResult writeResult = System.socketWrite(pair.first, data, 0);
      expect(writeResult.status, equals(ZX.OK));

      final ReadResult readResult =
          System.socketRead(pair.second, data.lengthInBytes);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals(data.lengthInBytes));
      expect(readResult.bytes.lengthInBytes, equals(data.lengthInBytes));
      expect(readResult.bytesAsUTF8String(), equals('Hello, world'));
    });

    test('partial read socket', () {
      final HandlePairResult pair = System.socketCreate();
      final ByteData data = utf8Bytes('Hello, world');
      final WriteResult writeResult = System.socketWrite(pair.first, data, 0);
      expect(writeResult.status, equals(ZX.OK));

      const int shortLength = 'Hello'.length;
      final ReadResult shortReadResult =
          System.socketRead(pair.second, shortLength);
      expect(shortReadResult.status, equals(ZX.OK));
      expect(shortReadResult.numBytes, equals(shortLength));
      expect(shortReadResult.bytes.lengthInBytes, equals(shortLength));
      expect(shortReadResult.bytesAsUTF8String(), equals('Hello'));

      final int longLength = data.lengthInBytes * 2;
      final ReadResult longReadResult =
          System.socketRead(pair.second, longLength);
      expect(longReadResult.status, equals(ZX.OK));
      expect(longReadResult.numBytes, equals(data.lengthInBytes - shortLength));
      expect(longReadResult.bytes.lengthInBytes, equals(longLength));
      expect(longReadResult.bytesAsUTF8String(), equals(', world'));
    });

    test('partial write socket', () {
      final HandlePairResult pair = System.socketCreate();
      final WriteResult writeResult1 =
          System.socketWrite(pair.first, utf8Bytes('Hello, '), 0);
      expect(writeResult1.status, equals(ZX.OK));
      final WriteResult writeResult2 =
          System.socketWrite(pair.first, utf8Bytes('world'), 0);
      expect(writeResult2.status, equals(ZX.OK));

      final ReadResult readResult = System.socketRead(pair.second, 100);
      expect(readResult.status, equals(ZX.OK));
      expect(readResult.numBytes, equals('Hello, world'.length));
      expect(readResult.bytes.lengthInBytes, equals(100));
      expect(readResult.bytesAsUTF8String(), equals('Hello, world'));
    });

    test('async wait socket read', () async {
      final HandlePairResult pair = System.socketCreate();
      final Completer<int> completer = Completer<int>();
      pair.first.asyncWait(ZX.SOCKET_READABLE, (int status, int pending) {
        completer.complete(status);
      });

      expect(completer.isCompleted, isFalse);

      System.socketWrite(pair.second, utf8Bytes('Hi'), 0);

      final int status = await completer.future;
      expect(status, equals(ZX.OK));
    });

    test('async wait socket closed', () async {
      final HandlePairResult pair = System.socketCreate();
      final Completer<int> completer = Completer<int>();
      pair.first.asyncWait(ZX.SOCKET_PEER_CLOSED, (int status, int pending) {
        completer.complete(status);
      });

      expect(completer.isCompleted, isFalse);

      pair.second.close();

      final int status = await completer.future;
      expect(status, equals(ZX.OK));
    });
  });

  group('vmo', () {
    test('fromFile', () {
      const String fuchsia = 'Fuchsia';
      File f = File('tmp/testdata')
        ..createSync()
        ..writeAsStringSync(fuchsia);
      String readFuchsia = f.readAsStringSync();
      expect(readFuchsia, equals(fuchsia));

      FromFileResult fileResult = System.vmoFromFile('tmp/testdata');
      expect(fileResult.status, equals(ZX.OK));
      MapResult mapResult = System.vmoMap(fileResult.handle);
      expect(mapResult.status, equals(ZX.OK));
      Uint8List fileData = UnmodifiableUint8ListView(mapResult.data);
      String fileString = utf8.decode(fileData.sublist(0, fileResult.numBytes));
      expect(fileString, equals(fuchsia));
    });

    test('duplicate', () {
      const String fuchsia = 'Fuchsia';
      Uint8List data = Uint8List.fromList(fuchsia.codeUnits);
      HandleResult createResult = System.vmoCreate(data.length);
      expect(createResult.status, equals(ZX.OK));
      int writeResult = System.vmoWrite(createResult.handle, 0,
                                        data.buffer.asByteData());
      expect(writeResult, equals(ZX.OK));
      Handle duplicate = createResult.handle.duplicate(ZX.RIGHTS_BASIC |
                                                       ZX.RIGHT_READ |
                                                       ZX.RIGHT_MAP);
      expect(duplicate.isValid, isTrue);

      // Read from the duplicate.
      MapResult mapResult = System.vmoMap(duplicate);
      expect(mapResult.status, equals(ZX.OK));
      Uint8List vmoData = UnmodifiableUint8ListView(mapResult.data);
      String vmoString = utf8.decode(vmoData.sublist(0, data.length));
      expect(vmoString, equals(fuchsia));
    });
  });
}
