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 |