| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE.md file. | |
| 4 | |
| 5 /// Provides common infrastructure for reading commads from a [Stream]. | |
| 6 /// | |
| 7 /// We have two independent command kinds that follow the same scheme: | |
| 8 /// | |
| 9 /// 1. ../vm_commands.dart | |
| 10 /// 2. hub/client_commands.dart | |
| 11 /// | |
| 12 /// Both commands are serialized in this format (using little endian): | |
| 13 /// | |
| 14 /// * Byte offset 0: one byte (code) which corresponds to an enum value. | |
| 15 /// * Byte offset 1: four bytes payload length (unsigned int). | |
| 16 /// * Byte offset 5: payload length bytes of payload. | |
| 17 library fletchc.src.command_transformer_builder; | |
| 18 | |
| 19 import 'dart:async' show | |
| 20 EventSink, | |
| 21 StreamTransformer; | |
| 22 | |
| 23 import 'dart:convert' show | |
| 24 ASCII; | |
| 25 | |
| 26 import 'dart:io' show | |
| 27 BytesBuilder; | |
| 28 | |
| 29 import 'dart:typed_data' show | |
| 30 ByteData, | |
| 31 Endianness, | |
| 32 TypedData, | |
| 33 Uint32List, | |
| 34 Uint8List; | |
| 35 | |
| 36 const Endianness commandEndianness = Endianness.LITTLE_ENDIAN; | |
| 37 | |
| 38 /// 32 bit package length + 8 bit code is 5 bytes. | |
| 39 const headerSize = 5; | |
| 40 | |
| 41 /// [C] is the command implementation class. | |
| 42 abstract class CommandTransformerBuilder<C> { | |
| 43 StreamTransformer<List<int>, C> build() { | |
| 44 BytesBuilder builder = new BytesBuilder(copy: false); | |
| 45 | |
| 46 ByteData toByteData(TypedData data, [int offset = 0, int length]) { | |
| 47 return data.buffer.asByteData(data.offsetInBytes + offset, length); | |
| 48 } | |
| 49 | |
| 50 void handleData(Uint8List data, EventSink<C> sink) { | |
| 51 builder.add(toUint8ListView(data)); | |
| 52 Uint8List list = builder.takeBytes(); | |
| 53 | |
| 54 ByteData view = toByteData(list); | |
| 55 | |
| 56 while (view.lengthInBytes >= headerSize) { | |
| 57 int length = view.getUint32(0, commandEndianness); | |
| 58 if ((view.lengthInBytes - headerSize) < length) { | |
| 59 // Not all of the payload has arrived yet. | |
| 60 break; | |
| 61 } | |
| 62 int code = view.getUint8(4); | |
| 63 | |
| 64 ByteData payload = toByteData(view, headerSize, length); | |
| 65 | |
| 66 C command = makeCommand(code, payload); | |
| 67 | |
| 68 if (command != null) { | |
| 69 sink.add(command); | |
| 70 } else { | |
| 71 sink.addError("Command not implemented yet: $code"); | |
| 72 } | |
| 73 | |
| 74 view = toByteData(payload, length); | |
| 75 } | |
| 76 | |
| 77 if (view.lengthInBytes > 0) { | |
| 78 builder.add(toUint8ListView(view)); | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 void handleError(error, StackTrace stackTrace, EventSink<C> sink) { | |
| 83 sink.addError(error, stackTrace); | |
| 84 } | |
| 85 | |
| 86 void handleDone(EventSink<C> sink) { | |
| 87 List trailing = builder.takeBytes(); | |
| 88 if (trailing.length != 0) { | |
| 89 sink.addError( | |
| 90 new StateError("Stream closed with trailing bytes : $trailing")); | |
| 91 } | |
| 92 sink.close(); | |
| 93 } | |
| 94 | |
| 95 return new StreamTransformer<List<int>, C>.fromHandlers( | |
| 96 handleData: handleData, | |
| 97 handleError: handleError, | |
| 98 handleDone: handleDone); | |
| 99 } | |
| 100 | |
| 101 C makeCommand(int code, ByteData payload); | |
| 102 } | |
| 103 | |
| 104 Uint8List toUint8ListView(TypedData list, [int offset = 0, int length]) { | |
| 105 if (length == null) { | |
| 106 length = list.lengthInBytes; | |
| 107 } | |
| 108 return new Uint8List.view(list.buffer, list.offsetInBytes + offset, length); | |
| 109 } | |
| 110 | |
| 111 /// [E] is an enum. | |
| 112 class CommandBuffer<E> { | |
| 113 int position = headerSize; | |
| 114 | |
| 115 Uint8List list = new Uint8List(16); | |
| 116 | |
| 117 ByteData view; | |
| 118 | |
| 119 CommandBuffer() { | |
| 120 view = new ByteData.view(list.buffer, list.offsetInBytes); | |
| 121 } | |
| 122 | |
| 123 void growBytes(int size) { | |
| 124 while (position + size >= list.length) { | |
| 125 list = new Uint8List(list.length * 2) | |
| 126 ..setRange(0, list.length, list); | |
| 127 view = new ByteData.view(list.buffer, list.offsetInBytes); | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 void addUint8(int value) { | |
| 132 growBytes(1); | |
| 133 view.setUint8(position++, value); | |
| 134 } | |
| 135 | |
| 136 void addUint32(int value) { | |
| 137 // TODO(ahe): The C++ appears to often read 32-bit values into a signed | |
| 138 // integer. Figure which is signed and which is unsigned. | |
| 139 growBytes(4); | |
| 140 view.setUint32(position, value, commandEndianness); | |
| 141 position += 4; | |
| 142 } | |
| 143 | |
| 144 void addUint64(int value) { | |
| 145 growBytes(8); | |
| 146 view.setUint64(position, value, commandEndianness); | |
| 147 position += 8; | |
| 148 } | |
| 149 | |
| 150 void addDouble(double value) { | |
| 151 growBytes(8); | |
| 152 view.setFloat64(position, value, commandEndianness); | |
| 153 position += 8; | |
| 154 } | |
| 155 | |
| 156 void addUint8List(List<int> value) { | |
| 157 growBytes(value.length); | |
| 158 list.setRange(position, position + value.length, value); | |
| 159 position += value.length; | |
| 160 } | |
| 161 | |
| 162 void addAsciiString(String value) { | |
| 163 addUint8List(ASCII.encode(value)); | |
| 164 } | |
| 165 | |
| 166 void addBool(bool value) { | |
| 167 addUint8(value ? 1 : 0); | |
| 168 } | |
| 169 | |
| 170 void sendOn(Sink<List<int>> sink, E code) { | |
| 171 view.setUint32(0, position - headerSize, commandEndianness); | |
| 172 view.setUint8(4, (code as dynamic).index); | |
| 173 sink.add(new Uint8List.view(list.buffer, list.offsetInBytes, position)); | |
| 174 } | |
| 175 | |
| 176 static bool readBoolFromBuffer(Uint8List buffer, int offset) { | |
| 177 return buffer[offset] != 0; | |
| 178 } | |
| 179 | |
| 180 static String readStringFromBuffer(Uint8List buffer, int offset, int length) { | |
| 181 const bytesPerCharacter = 4; | |
| 182 int numberOfCharacters = length ~/ bytesPerCharacter; | |
| 183 assert(length == numberOfCharacters * bytesPerCharacter); | |
| 184 if (((buffer.offsetInBytes + offset) % Uint32List.BYTES_PER_ELEMENT) != 0) { | |
| 185 // Uint32List.view throws ArgumentError if offset isn't muliple of 4. | |
| 186 buffer = new Uint8List.fromList(buffer); | |
| 187 } | |
| 188 return new String.fromCharCodes( | |
| 189 new Uint32List.view( | |
| 190 buffer.buffer, buffer.offsetInBytes + offset, numberOfCharacters)); | |
| 191 } | |
| 192 | |
| 193 static String readAsciiStringFromBuffer( | |
| 194 Uint8List buffer, int offset, int length) { | |
| 195 return new String.fromCharCodes( | |
| 196 new Uint8List.view( | |
| 197 buffer.buffer, buffer.offsetInBytes + offset, length)); | |
| 198 } | |
| 199 | |
| 200 static int readInt32FromBuffer(Uint8List buffer, int offset) { | |
| 201 return buffer.buffer.asByteData(buffer.offsetInBytes) | |
| 202 .getInt32(offset, commandEndianness); | |
| 203 } | |
| 204 | |
| 205 static int readInt64FromBuffer(Uint8List buffer, int offset) { | |
| 206 return buffer.buffer.asByteData(buffer.offsetInBytes) | |
| 207 .getInt64(offset, commandEndianness); | |
| 208 } | |
| 209 | |
| 210 static double readDoubleFromBuffer(Uint8List buffer, int offset) { | |
| 211 { | |
| 212 // TODO(ahe): On ARM the Dart VM might crash when reading a double from | |
| 213 // an unaligned address. Remove this block when this bug is fixed: | |
| 214 // https://github.com/dart-lang/sdk/issues/23953 | |
| 215 buffer = new Uint8List.fromList( | |
| 216 buffer.buffer.asUint8List(buffer.offsetInBytes + offset)); | |
| 217 assert(buffer.offsetInBytes == 0); | |
| 218 offset = 0; | |
| 219 } | |
| 220 return buffer.buffer.asByteData(buffer.offsetInBytes) | |
| 221 .getFloat64(offset, commandEndianness); | |
| 222 } | |
| 223 } | |
| OLD | NEW |