GitHub PullRequestReviewCommentEvent at 2014-03-03 18:10:27
Repository: spark
Actor: shepheb
Payload: {"comment":{"url":"https://api.github.com/repos/dart-lang/spark/pulls/comments/10221082","id":10221082,"diff_hunk":"@@ -0,0 +1,707 @@\n+// Copyright (c) 2014, Google Inc. Please see the AUTHORS file for details.\n+// All rights reserved. Use of this source code is governed by a BSD-style\n+// license that can be found in the LICENSE file.\n+\n+library spark.adb;\n+\n+import 'dart:async';\n+import 'dart:collection';\n+import 'dart:convert';\n+import 'dart:typed_data';\n+\n+import 'package:bignum/bignum.dart';\n+import 'package:chrome/chrome_app.dart' as chrome;\n+import 'package:cipher/cipher.dart';\n+import 'package:cipher/impl/base.dart' as CipherBase;\n+import 'package:crypto/crypto.dart' show CryptoUtils;\n+\n+import '../spark_model.dart';\n+\n+// Most of these are helper classes that don't really need to be exported,\n+// and might be better off in an internal library.\n+class AdbUtil {\n+  // ADB command names\n+  static final int A_SYNC = 0x434e5953;\n+  static final int A_CNXN = 0x4e584e43;\n+  static final int A_OPEN = 0x4e45504f;\n+  static final int A_OKAY = 0x59414b4f;\n+  static final int A_CLSE = 0x45534c43;\n+  static final int A_WRTE = 0x45545257;\n+  static final int A_AUTH = 0x48545541;\n+\n+  // ADB protocol version\n+  static final int A_VERSION = 0x01000000;\n+\n+  static final int MESSAGE_SIZE_PAYLOAD = 24;\n+  static final int MAX_PAYLOAD = 4096;\n+  static final int ADB_VERSION_MINOR = 0; // Used for help/version info.\n+\n+  // Increment this when we want to force users to start a new ADB server.\n+  static final int ADB_SERVER_VERSION = 31;\n+\n+  // USB information\n+  static final int ADB_CLASS = 0xff;\n+  static final int ADB_SUBCLASS = 0x42;\n+  static final int ADB_PROTOCOL = 0x1;\n+\n+  // Types for AUTH commands\n+  static final int ADB_AUTH_TOKEN = 1;\n+  static final int ADB_AUTH_SIGNATURE = 2;\n+  static final int ADB_AUTH_RSAPUBLICKEY = 3;\n+\n+  // Some helper functions.\n+  static int checksum(ByteData data) {\n+    int result = 0;\n+    var buffer = new Uint8List.view(data.buffer);\n+    for (int i = 0; i < buffer.length; i++) {\n+      int x = buffer[i];\n+      if (x < 0) {\n+        x += 256;\n+      }\n+      result += x;\n+    }\n+    return result;\n+  }\n+}\n+\n+class SystemIdentity {\n+  // Can be 'host', 'bootloader', 'device'.\n+  String systemType = 'host';\n+  String serialno = '';\n+  String banner = '';\n+  String toString() => '${systemType}:${serialno}:${banner}';\n+}\n+\n+class AdbMessage {\n+  /*\n+  struct message {\n+    unsigned command;       // command identifier constant\n+    unsigned arg0;          // first argument\n+    unsigned arg1;          // second argument\n+    unsigned data_length;   // length of payload (0 is allowed)\n+    unsigned data_crc32;    // crc32 of data payload\n+    unsigned magic;         // command ^ 0xffffffff\n+  };\n+  */\n+\n+  static final int _COMMAND_OFFSET = 0;\n+  static final int _ARG0_OFFSET = 4;\n+  static final int _ARG1_OFFSET = 8;\n+  static final int _DATA_LENGTH_OFFSET = 12;\n+  static final int _CHECKSUM_OFFSET = 16;\n+  static final int _MAGIC_OFFSET = 20;\n+\n+  ByteData messageBuffer = new ByteData(AdbUtil.MESSAGE_SIZE_PAYLOAD);\n+  ByteData dataBuffer;\n+\n+  int _getInt32(offset) =>\n+      messageBuffer.getInt32(offset, Endianness.LITTLE_ENDIAN);\n+\n+  void _setInt32(offset, value) =>\n+      messageBuffer.setInt32(offset, value, Endianness.LITTLE_ENDIAN);\n+\n+  int  get command => _getInt32(_COMMAND_OFFSET);\n+  void set command(int value) => _setInt32(_COMMAND_OFFSET, value);\n+  int  get arg0 => _getInt32(_ARG0_OFFSET);\n+  void set arg0(int value) => _setInt32(_ARG0_OFFSET, value);\n+  int  get arg1 => _getInt32(_ARG1_OFFSET);\n+  void set arg1(int value) => _setInt32(_ARG1_OFFSET, value);\n+  int  get dataLength => _getInt32(_DATA_LENGTH_OFFSET);\n+  void set dataLength(int value) => _setInt32(_DATA_LENGTH_OFFSET, value);\n+  int  get dataCrc32 => _getInt32(_CHECKSUM_OFFSET);\n+  void set dataCrc32(int value) => _setInt32(_CHECKSUM_OFFSET, value);\n+  int  get magic => _getInt32(_MAGIC_OFFSET);\n+  void set magic(int value) => _setInt32(_MAGIC_OFFSET, value);\n+\n+  AdbMessage(int command, int arg0, int arg1, [String data = null]) {\n+    if (data != null) {\n+      // Build a dataBuffer.\n+      dataBuffer = new ByteData(data.length);\n+      Uint8List u8data = new Uint8List.fromList(data.codeUnits);\n+      Uint8List u8DataBufferView = new Uint8List.view(dataBuffer.buffer);\n+      for (int i = 0; i < u8data.length; i++) {\n+        u8DataBufferView[i] = u8data[i];\n+      }\n+    }\n+\n+    this.command = command;\n+    this.arg0 = arg0;\n+    this.arg1 = arg1;\n+    this.dataLength = data != null ? data.length : 0;\n+    this.dataCrc32 = data != null ? AdbUtil.checksum(dataBuffer) : 0;\n+    this.magic = command ^ 0xffffffff;\n+  }\n+\n+  AdbMessage.fromMessageBufferBytes(List<int> bytes) {\n+    Uint8List u8data = new Uint8List.fromList(bytes);\n+    Uint8List u8MessageBufferView = new Uint8List.view(messageBuffer.buffer);\n+    for (int i = 0; i < u8data.length; i++) {\n+      u8MessageBufferView[i] = u8data[i];\n+    }\n+  }\n+\n+  // Read into dataBuffer.\n+  // To be used on receiving the data of an incoming message.\n+  void loadDataBuffer(List<int> data) {\n+    // Rely on the messageBuffer's information.\n+    dataBuffer = new ByteData(dataLength);\n+    Uint8List u8data = new Uint8List.fromList(data);\n+    Uint8List u8DataBufferView = new Uint8List.view(dataBuffer.buffer);\n+    for (int i = 0; i < u8data.length; i++) {\n+      u8DataBufferView[i] = u8data[i];\n+    }\n+  }\n+\n+  // To be used on freshly created messages, that don't have their length or crc\n+  // fields set yet.\n+  void setData(ByteData data) {\n+    dataBuffer = new ByteData(data.lengthInBytes);\n+    Uint8List u8data = new Uint8List.view(data.buffer);\n+    Uint8List u8DataBufferView = new Uint8List.view(dataBuffer.buffer);\n+    for (int i = 0; i < u8data.length; i++) {\n+      u8DataBufferView[i] = u8data[i];\n+    }\n+\n+    dataLength = data.lengthInBytes;\n+    dataCrc32 = AdbUtil.checksum(data);\n+  }\n+\n+\n+  String toString() {\n+    StringBuffer sb = new StringBuffer()\n+    ..write('[command = ${command.toRadixString(16)}, ')\n+    ..write('arg0 = ${arg0.toRadixString(16)}, ')\n+    ..write('arg1 = ${arg1.toRadixString(16)}, ')\n+    ..write('dataLength = ${dataLength.toRadixString(16)}, ')\n+    ..write('dataCrc32 = ${dataCrc32.toRadixString(16)}, ')\n+    ..write('magic = ${magic.toRadixString(16)}]');\n+\n+    if (dataBuffer != null) {\n+      sb.write('[');\n+      Uint8List u8DataBufferView = new Uint8List.view(dataBuffer.buffer);\n+      for (int i = 0; i < u8DataBufferView.length; i++) {\n+        if (i+1 != u8DataBufferView.length) {\n+          sb.write('${u8DataBufferView[i].toRadixString(16)}, ');\n+        } else {\n+          sb.write('${u8DataBufferView[i].toRadixString(16)}]');\n+        }\n+      }\n+    }\n+\n+    return sb.toString();\n+  }\n+}\n+\n+class AndroidDevice {\n+  chrome.Device androidDevice;\n+  chrome.InterfaceDescriptor adbInterface;\n+  chrome.EndpointDescriptor inDescriptor;\n+  chrome.EndpointDescriptor outDescriptor;\n+  chrome.ConnectionHandle adbConnectionHandle;\n+\n+  String deviceToken = 'DEVICE TOKEN NOT SET';\n+\n+  AndroidDevice() {\n+    // The cipher library needs initializing before it can be used.\n+    // This function is safe to call multiple times.\n+    CipherBase.initCipher();\n+  }\n+\n+  Future listDevices() {\n+    // TODO (shepheb): List all Android devices.\n+  }\n+\n+  // Either resolves on successful open, or rejects with an error message on failure.\n+  Future open(vendorId, productId) {\n+    chrome.EnumerateDevicesOptions edo =\n+        new chrome.EnumerateDevicesOptions(vendorId: vendorId, productId:\n+            productId);\n+\n+    // Get the devices with the given vendor id and product id.\n+    return chrome.usb.getDevices(edo).then((List<chrome.Device> devices) {\n+      // For each such device, look for a specific device with ADB class,\n+      // subclass and protocol.\n+      // TODO(shepheb): Handle finding multiple ADB devices.\n+      // NB: The function inside the forEach below uses errors to indicate\n+      // success, when it finds a valid device. This is a bit of a hack, but it\n+      // works. Real errors are String messages. \"Successes\" are null.\n+      return Future.forEach(devices, (chrome.Device device) {\n+        chrome.EnumerateDevicesAndRequestAccessOptions opts =\n+            new chrome.EnumerateDevicesAndRequestAccessOptions(\n+                vendorId: device.vendorId, productId: device.productId);\n+        return chrome.usb.findDevices(opts)\n+        .then((List<chrome.ConnectionHandle> connections) {\n+          return Future.forEach(connections, (chrome.ConnectionHandle handle) {\n+            // List all interfaces on this connection handle.\n+            return chrome.usb.listInterfaces(handle)\n+            .then((List<chrome.InterfaceDescriptor> interfaces) {\n+              // For each interface, find the match for ADB class, subclass and\n+              // protocol.\n+              interfaces.forEach((interface) {\n+                // Check to make sure interface class, subclass and protocol\n+                // match ADB.\n+                // Avoid opening mass storage endpoints (they're slow).\n+                if (interface.interfaceClass == AdbUtil.ADB_CLASS &&\n+                    interface.interfaceSubclass == AdbUtil.ADB_SUBCLASS &&\n+                    interface.interfaceProtocol == AdbUtil.ADB_PROTOCOL) {\n+                  adbInterface = interface;\n+                  androidDevice = device;\n+                  this.adbConnectionHandle = handle;\n+                }\n+\n+                interface.endpoints.forEach((chrome.EndpointDescriptor desc) {\n+                  // Find the input and output descriptors.\n+                  if (desc.direction == chrome.Direction.IN &&\n+                      interface.interfaceClass == AdbUtil.ADB_CLASS &&\n+                      interface.interfaceSubclass == AdbUtil.ADB_SUBCLASS &&\n+                      interface.interfaceProtocol == AdbUtil.ADB_PROTOCOL) {\n+                    inDescriptor = desc;\n+                  } else if (desc.direction == chrome.Direction.OUT &&\n+                      interface.interfaceClass == AdbUtil.ADB_CLASS &&\n+                      interface.interfaceSubclass == AdbUtil.ADB_SUBCLASS &&\n+                      interface.interfaceProtocol == AdbUtil.ADB_PROTOCOL) {\n+                    outDescriptor = desc;\n+                  }\n+                });\n+              });\n+\n+              // Check if all parts of the device got assigned.\n+              if (adbInterface != null && androidDevice != null &&\n+                  this.adbConnectionHandle != null && inDescriptor != null &&\n+                  outDescriptor != null) {\n+                // Throw here to bail out of iterating through the devices.\n+                // It's not really an error.\n+                throw null;\n+              }\n+              // XXX: The original sets completer.complete(false) here,\n+              // but I'm not convinced that's correct.\n+            });\n+          });\n+        });\n+      }).then((_) {\n+        // If the above completed \"successfully\", then no device was found.\n+        throw 'No Android device found.';\n+      }).catchError((e) {\n+        if (e == null || e is NullThrownError) {\n+          // Turn an empty error into an empty success, since we found our\n+          // Android device.\n+          return null;\n+        } else {\n+          // Otherwise the error is real, and we should re-throw.\n+          throw e;\n+        }\n+      });\n+    });\n+  }\n+\n+  // This either resolves with a TransferResultInfo, or fails with an error\n+  // message.\n+  Future<chrome.TransferResultInfo> sendBuffer(ByteData buffer) {\n+    chrome.ArrayBuffer arrayBuffer = new chrome.ArrayBuffer.fromBytes(\n+        new Uint8List.view(buffer.buffer).toList());\n+\n+    chrome.GenericTransferInfo transferInfo = new chrome.GenericTransferInfo()\n+        ..direction = outDescriptor.direction\n+        ..endpoint = outDescriptor.address\n+        ..length = arrayBuffer.getBytes().length\n+        ..data = arrayBuffer;\n+\n+    return chrome.usb.bulkTransfer(adbConnectionHandle, transferInfo)\n+        .then((chrome.TransferResultInfo result) {\n+      if (result.resultCode != 0) {\n+        throw 'Failed to send';\n+      } else {\n+        return result;\n+      }\n+    });\n+  }\n+\n+  // Sends both the message and data portions.\n+  // The future either resolves with a TransferResultInfo, or fails with an\n+  // error message.\n+  Future<chrome.TransferResultInfo> sendMessage(AdbMessage message) {\n+    print('> ' + message.toString());\n+    return sendBuffer(message.messageBuffer).then(\n+        (chrome.TransferResultInfo result) {\n+      return message.dataLength > 0 ? sendBuffer(message.dataBuffer) : result;\n+    });\n+  }\n+\n+\n+  // Connects to and authenticates with the device.\n+  Future connect(SystemIdentity systemIdentity) {\n+    return chrome.usb.claimInterface(adbConnectionHandle,\n+        adbInterface.interfaceNumber).then((_) {\n+      AdbMessage adbMessage = new AdbMessage(AdbUtil.A_CNXN, AdbUtil.A_VERSION,\n+          AdbUtil.MAX_PAYLOAD, systemIdentity.toString());\n+\n+      // Transfer connect message.\n+      return sendMessage(adbMessage).then((_) {\n+        return expectConnectionMessage();\n+      });\n+    });\n+  }\n+\n+  Future<AsymmetricKeyPair> getKeys(ByteData seedToken) {\n+    return SparkModel.instance.localPrefs.getValue('adb_rsa_keys')\n+    .then((jsonKeys) {\n+      if (jsonKeys == null) {\n+        throw 'catchError below will generate keys';\n+      }\n+      return _deserializeRSAKeys(jsonKeys);\n+    }).catchError((e) {\n+      // If we can't retrieve the public and private keys, create and store a\n+      // new pair.\n+      // ADB uses 2048-bit RSA keys.\n+      RSAKeyGeneratorParameters params = new RSAKeyGeneratorParameters(\n+          new BigInteger(257), 2048, 90);\n+\n+      SecureRandom rnd = new SecureRandom('AES/CTR/PRNG');\n+      // Using the random token from the device as the seed.\n+      ParametersWithIV rndParams = new ParametersWithIV(\n+          new KeyParameter(new Uint8List.view(seedToken.buffer, 0, 16)),\n+          new Uint8List(16));\n+      rnd.seed(rndParams);\n+\n+      ParametersWithRandom paramsWithRandom = new ParametersWithRandom(params,\n+          rnd);\n+      KeyGenerator gen = new KeyGenerator(\"RSA\");\n+      gen.init(paramsWithRandom);\n+      AsymmetricKeyPair keys = gen.generateKeyPair();\n+      return SparkModel.instance.localPrefs.setValue('adb_rsa_keys',\n+          _serializeRSAKeys(keys)).then((_) => keys);\n+    });\n+  }\n+\n+  // Waits to receive either an ATUH or CNXN message. Handles AUTH as needed.\n+  // Resolves when the handshake is done.\n+  Future expectConnectionMessage() {\n+    bool sentSignature = false;\n+    bool sentPubKey = false;\n+    AsymmetricKeyPair keys;\n+    Future handleConnectionMessage() {\n+      return receive().then((msg) {\n+        if (msg.command == AdbUtil.A_AUTH) {\n+          // We have been given a token.\n+          // If we haven't tried the keys yet, try them.\n+          if (!sentSignature) {\n+            // Fetch the keys, supplying the random token from the device as the\n+            // seed for the random number generator.\n+            return getKeys(msg.dataBuffer)\n+            .then((keys_) {\n+              keys = keys_;\n+\n+              // First we create a SecureRandom, using the latter 16 bytes of\n+              // the token as the seed. This is to keep it from being the same\n+              // as the key generator seed, which would have been the first 16.\n+              SecureRandom rnd = new SecureRandom('AES/CTR/PRNG');\n+              ParametersWithIV rndParams = new ParametersWithIV(\n+                  new KeyParameter(new Uint8List.view(\n+                      msg.dataBuffer.buffer, 4, 16)),\n+                  new Uint8List(16));\n+              rnd.seed(rndParams);\n+\n+              // Next we prepare the Signer.\n+              PrivateKeyParameter<RSAPrivateKey> pkParam =\n+                  new PrivateKeyParameter<RSAPrivateKey>(keys.privateKey);\n+              ParametersWithRandom params = new ParametersWithRandom(pkParam,\n+                  rnd);\n+              Signer signer = new Signer(\"SHA-1/RSA\");\n+              signer.init(true, params);\n+\n+              // And finally create the signature.\n+              RSASignature sig = signer.generateSignature(\n+                  new Uint8List.view(msg.dataBuffer.buffer));\n+              // And send back the signed token.\n+              AdbMessage response = new AdbMessage(AdbUtil.A_AUTH,\n+                  AdbUtil.ADB_AUTH_SIGNATURE, 0);\n+              response.setData(new ByteData.view(sig.bytes.buffer));\n+              sentSignature = true;\n+              return sendMessage(response).then((_) {\n+                return handleConnectionMessage();\n+              });\n+            });\n+          } else if (!sentPubKey) {\n+            // If we have tried the keys, then send our public key.\n+            // The public key consists of the following:\n+            // - Length of key in words (64, for 2048-bit keys), 32-bit LE.\n+            // - Inverse of the least-significant 32 bits of the modulus.\n+            // - The modulus itself, little endian.\n+            // - 2^4096 mod n, also little endian.\n+            // - The exponent, same as is used in the key generation above.\n+            //   (32-bit little endian).\n+            // Which is all Base64-encoded and has a user@host username\n+            // appended. That may be important, so I'll include one.\n+\n+            // Why this format? I have no idea. Especially the r^2 thing,\n+            // which can be recomputed from the modulus.\n+            ByteData data = new ByteData(4 + 4 + 256 + 256 + 4);\n+            // First compute the little checksum that goes near the beginning.\n+            // It's the multiplicative inverse of the first 32 bits of the\n+            // modulus.\n+            BigInteger modulus = keys.publicKey.modulus;\n+            BigInteger twoTo32 = BigInteger.TWO.pow(32);\n+            BigInteger firstWord = modulus.mod(twoTo32);\n+            BigInteger negated = twoTo32 - firstWord;\n+            BigInteger inverse = negated.modInverse(twoTo32);\n+\n+            // Write in the length, inverse, and the exponent.\n+            data.setUint32(0, 64, Endianness.LITTLE_ENDIAN);\n+            data.setUint32(4, inverse.intValue(), Endianness.LITTLE_ENDIAN);\n+            data.setUint32(4+4+256+256, keys.publicKey.exponent.intValue(),\n+                Endianness.LITTLE_ENDIAN);\n+\n+            // Write the modulus. It gets returned as a byte array, but it's\n+            // big-endian. We want it little-endian, so we write it backwards.\n+            List<int> beModulus = modulus.toByteArray();\n+            int top = 256 + 4 + 4 - 1;\n+            // HACK: For some reason, the BigInteger for the modulus is 257\n+            // bytes long, with an extra 0 in its MSB. So we skip over it.\n+            // Hopefully this applies to all keys, but it could be made more\n+            // robust, always copying the least-significant 256 bytes from the\n+            // array.\n+            int extra = beModulus.length - 256;\n+            for (int i = extra; i < beModulus.length; i++) {\n+              data.setUint8(top - (i - extra), beModulus[i]);\n+            }\n+\n+            // Now the weird part: computing what's called r^2.\n+            // This is (2^2048)^2 % modulus.\n+            BigInteger r2 = BigInteger.TWO.pow(4096).mod(modulus);\n+            List<int> ber2 = r2.toByteArray();\n+            top = 256 + 256 + 4 + 4 - 1;\n+            extra = ber2.length - 256;\n+            for (int i = extra; i < ber2.length; i++) {\n+              data.setUint8(top - (i - extra), ber2[i]);\n+            }\n+\n+            // Convert the binary data to a Base64 String.\n+            String b64data = CryptoUtils.bytesToBase64(\n+                new Uint8List.view(data.buffer).toList());\n+\n+            // Now the payload is ready, so we reply with the message.\n+            AdbMessage response = new AdbMessage(AdbUtil.A_AUTH,\n+                AdbUtil.ADB_AUTH_RSAPUBLICKEY, 0,\n+                \"$b64data braden@braden-macbookair\\x00\");\n+            Uint8List temp = new Uint8List.view(response.dataBuffer.buffer);\n+            sentPubKey = true;\n+            return sendMessage(response).then((_) {\n+              return handleConnectionMessage();\n+            });\n+          } else {\n+            // We've tried signing, and tried sending our pubkey, and are\n+            // still not authenticated? Something went wrong.\n+            throw 'Got AUTH TOKEN command even after RSAPUBKEY response.';\n+          }\n+        } else if (msg.command == AdbUtil.A_CNXN) {\n+          // If we receive a CNXN in response, then we're authenticated!\n+          // There's nothing to handle in this message, so we just return.\n+          return null;\n+        } else {\n+          // Unexpected message type. Do nothing, and keep calling myself.\n+          return handleConnectionMessage();\n+        }\n+      });\n+    } // End of handleConnectionMessage.\n+\n+    // handleConnectionMessage will repeatedly call itself, eventually\n+    // returning when it is successfully authenticated.\n+    return handleConnectionMessage();\n+  }\n+\n+  // Either resolves with an AdbMessage (including data portion, if applicable),\n+  // or rejects with a chrome.TransferResultInfo giving the bad resultCode.\n+  Future<AdbMessage> receive() {\n+    AdbMessage readAdbMessage;\n+    chrome.GenericTransferInfo readTransferInfo =\n+        new chrome.GenericTransferInfo()\n+        ..direction = inDescriptor.direction\n+        ..endpoint = inDescriptor.address\n+        ..length = AdbUtil.MESSAGE_SIZE_PAYLOAD;\n+\n+    return chrome.usb.bulkTransfer(adbConnectionHandle, readTransferInfo)\n+        .then((chrome.TransferResultInfo readResult) {\n+      // Read back a MessageBuffer for AUTH response.\n+      if (readResult.resultCode != 0) {\n+        throw readResult;\n+      }\n+\n+      readAdbMessage = new AdbMessage.fromMessageBufferBytes(\n+          readResult.data.getBytes());\n+\n+      if (readAdbMessage.dataLength > 0) {\n+        chrome.GenericTransferInfo readDataInfo =\n+            new chrome.GenericTransferInfo()\n+            ..direction = inDescriptor.direction\n+            ..endpoint = inDescriptor.address\n+            ..length = readAdbMessage.dataLength;\n+\n+        return chrome.usb.bulkTransfer(adbConnectionHandle, readDataInfo)\n+            .then((chrome.TransferResultInfo readDataResult) {\n+          if (readDataResult.resultCode != 0) {\n+            throw readDataResult;\n+          }\n+          // TODO(shepheb): This is slow, copying through List<int>.\n+          // It would be much better to have a way to convert more directly.\n+          readAdbMessage.loadDataBuffer(readDataResult.data.getBytes());\n+          print('    < ' + readAdbMessage.toString());\n+          return readAdbMessage;\n+        });\n+      } else {\n+        print('    < ' + readAdbMessage.toString());\n+        return readAdbMessage;\n+      }\n+    });\n+  }\n+\n+  // Returns the ID of the remote end.\n+  // This is only relevant for the OKAY after an OPEN, but harmless to return\n+  // always.\n+  Future<int> awaitOkay() {\n+    return receive().then((msg) {\n+      if (msg.command == AdbUtil.A_OKAY) {\n+        return msg.arg0;\n+      } else throw 'Expected an OKAY but got: ${msg.toString()}';\n+    });\n+  }\n+\n+  // Expects the complete HTTP request as a List<int>, and the port as an int.\n+  // Sends a single HTTP request, reads a single response. Returns the complete\n+  // response, headers and all. In case of an error, rejects the Future with an\n+  // error message.\n+  // Technically this will work for sending any TCP request/response pair.\n+  Future<List<int>> sendHttpRequest(List<int> httpRequest, int port) {\n+    int localID = 4; // Arbitrary choice.\n+    int remoteID;\n+    Queue<ByteData> responseChunks;\n+\n+    // Send the open message.\n+    return sendMessage(new AdbMessage(AdbUtil.A_OPEN, localID, 0,\n+            'tcp:$port\\x00'))\n+        .then((_) { return awaitOkay(); })\n+        .catchError((e) {\n+          // If we caught an error here, we probably got a CLSE instead.\n+          // This means the remote end isn't listening to our port.\n+          throw 'Connection on port $port refused.';\n+        }).then((remoteID_) {\n+          remoteID = remoteID_;\n+\n+          // Prepare and send the parts of the message.\n+          int chunkCount = (httpRequest.length / AdbUtil.MAX_PAYLOAD).ceil();\n+          Iterable<ByteData> chunks = new Iterable.generate(chunkCount, (i) {\n+            int start = i * AdbUtil.MAX_PAYLOAD;\n+            int end = (i+1) * AdbUtil.MAX_PAYLOAD;\n+            end = end > httpRequest.length ? httpRequest.length : end;\n+\n+            ByteData chunk = new ByteData(end-start);\n+            for (int j = start; j < end; j++) {\n+              chunk.setUint8(j-start, httpRequest[j]);\n+            }\n+            return chunk;\n+          });\n+\n+          return Future.forEach(chunks, (packet) {\n+            // Prepare the message.\n+            AdbMessage msg = new AdbMessage(AdbUtil.A_WRTE, localID, remoteID);\n+            msg.setData(packet);\n+            return sendMessage(msg).then((_) {\n+              return awaitOkay();\n+            });\n+          });\n+        })\n+        .then((_) {\n+          // We've finished sending the request, now we await the response.\n+          // Since we don't know when the response is all done, we simply await\n+          // a 500ms pause after the last message.\n+          // TODO(shepheb): Maybe make this HTTP-specific and understand the\n+          // response and its Content-length?\n+          responseChunks = new Queue<ByteData>();\n+\n+          Future readChunk() {\n+            return receive().timeout(new Duration(milliseconds: 800))\n+                .then((msg) {\n+                  if (msg.command == AdbUtil.A_WRTE) {\n+                    print('Got chunk');\n+                    responseChunks.add(msg.dataBuffer);\n+                    return sendMessage(new AdbMessage(AdbUtil.A_OKAY, localID,\n+                        remoteID)).then((_) { return readChunk(); });\n+                  } else {\n+                    print ('Unexpected message type!');\n+                    throw 'Unexpected message from device.';\n+                  }\n+                }).catchError((e) {\n+                  if (e is TimeoutException) {\n+                    return null;\n+                  } else {\n+                    throw e;\n+                  }\n+                });\n+          };\n+\n+          return readChunk();\n+        }).then((_) {\n+          // Send a CLSE and expect a CLSE back.\n+          print('Sending CLSE');\n+          return sendMessage(new AdbMessage(AdbUtil.A_CLSE, 0, remoteID));\n+        }).then((_) {\n+          // Turn responseChunks into a List<int> and return it.\n+          List<int> ret = [];\n+          for (ByteData chunk in responseChunks) {\n+            for (int i = 0; i < chunk.lengthInBytes; i++) {\n+              ret.add(chunk.getUint8(i));\n+            }\n+          }\n+          return ret;\n+        });\n+        /*\n+        .then((_) {\n+          print('Waiting for CLSE response');\n+          return receive();\n+        }).then((msg) {\n+          print('Got remote (CLSE?) message');\n+          if (msg.command == AdbUtil.A_CLSE) {\n+            // Turn responseChunks into a List<int> and return it.\n+            List<int> ret = [];\n+            for (ByteData chunk in responseChunks) {\n+              for (int i = 0; i < chunk.lengthInBytes; i++) {\n+                ret.add(chunk.getUint8(i));\n+              }\n+            }\n+            return ret;\n+          } else throw 'Unexpected message from device.';\n+        }).whenComplete(() {\n+          print('bottom');\n+        });\n+        */\n+  }\n+\n+  void handleMessage() {\n+    // TODO(shepheb): Handle the incoming messages.\n+  }\n+}\n+\n+\n+// HACK: These access the internals of the bignum package's BigInteger class.","path":"ide/app/lib/adb.dart","position":684,"original_position":684,"commit_id":"3ee6b6fff00abe918fe2fa722f47e95c225ad87e","original_commit_id":"3ee6b6fff00abe918fe2fa722f47e95c225ad87e","user":{"login":"shepheb","id":157812,"avatar_url":"https://avatars.githubusercontent.com/u/157812","gravatar_id":"0633934352d815f3c7cb561dd9e44c53","url":"https://api.github.com/users/shepheb","html_url":"https://github.com/shepheb","followers_url":"https://api.github.com/users/shepheb/followers","following_url":"https://api.github.com/users/shepheb/following{/other_user}","gists_url":"https://api.github.com/users/shepheb/gists{/gist_id}","starred_url":"https://api.github.com/users/shepheb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shepheb/subscriptions","organizations_url":"https://api.github.com/users/shepheb/orgs","repos_url":"https://api.github.com/users/shepheb/repos","events_url":"https://api.github.com/users/shepheb/events{/privacy}","received_events_url":"https://api.github.com/users/shepheb/received_events","type":"User","site_admin":false},"body":"Ah, actually, this comment is no longer true. That was written when I was looking at an old version of BigInteger, with far fewer features. Now the code safely uses an exported API to serialize to a hex string and back.","created_at":"2014-03-03T18:10:27Z","updated_at":"2014-03-03T18:10:27Z","html_url":"https://github.com/dart-lang/spark/pull/1207#discussion_r10221082","pull_request_url":"https://api.github.com/repos/dart-lang/spark/pulls/1207","_links":{"self":{"href":"https://api.github.com/repos/dart-lang/spark/pulls/comments/10221082"},"html":{"href":"https://github.com/dart-lang/spark/pull/1207#discussion_r10221082"},"pull_request":{"href":"https://api.github.com/repos/dart-lang/spark/pulls/1207"}}}}