0
votes

I have a command line Dart script client interacting over a [dart:io] websocket with a Jetty server. I've implemented a custom message subprotocol that uses reflection (both sides) to exchange Dart objects with Java objects in Jetty using binary encoding. PODO -> Uint8List -> wire -> ByteBufer -> POJO (and in reverse for the return trip). The local round trip unit tests execute correctly on either side (i.e. PODO -> Uint8List -> PODO; POJO -> ByteBuffer -> POJO). I've tested the connection with a different service endpoint using a series of simple 'string' exchanges. The transmission from Dart to Jetty works but the response data, although correctly received, produces an odd type that I don't understand and which the decoder doesn't understand as either a Uint8List, ByteBuffer, etc.

Although I can't easily distill this into a small example, here is the relevant code and some output:

Dart Client:

WebSocket.connect(url).then((WebSocket socket) {
  _log.finer('connected');
  _websocket = socket
    ..listen(_onResponse, onError: (e, StackTrace st) => print('Session error: $e; $st'));
  ...
}

_onResponse(data) {
  print('response raw data: $data');
  InstanceMirror im = reflect(data);
  print('instance: $im');
  ...
  decode(data)
}

decode(Uint8List data) {
  var b = data.buffer;
  ByteData bd = new ByteData.view(b);
  int offset = 0;
  const ENDIANNESS = Endianness.LITTLE_ENDIAN;

  int msgLength = bd.getInt32(offset, ENDIANNESS); // is 6645122; should be 101
  ...
}

Output:

    response raw data: [101, 0, 0, 0, ...]
    instance: InstanceMirror on Instance of '_Uint8ArrayView'

The IntelliJ debugger shows:

    data = {List[id=1]} size = 101
    > im = {_LocalInstanceMirror[id=2]} InstanceMirror on Instance of '_Uint8ArrayView'
        _reflectee = {List[id=1]}
        _type = null
        hasReflectee = true
2

2 Answers

2
votes

Dart supports the general ByteBuffer type which just represents a list of bytes as you can see here:

https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:typed_data.ByteBuffer

As the ByteBuffer class is abstract, you create lists from either receiving a ByteBuffer object or by instantiating a new list. The list usually is fixed length and working with it is hard. That's why you can create views from a ByteBuffer. A view represents a subset of the ByteBuffers bytes, given by an offset and a length which could also be the whole buffer.

Let's look at a small example:

import 'dart:typed_data';

void main() {
  Uint8List data8 = new Uint8List.fromList([1,2,3,4,5,6,256]);
  Uint16List data16 = new Uint16List.fromList([1,2,3,4,5,6,256]);

  print(data8);
  print(data16);

  print(data8.buffer.lengthInBytes);
  print(data16.buffer.lengthInBytes);

  print(data16.buffer.asUint8List());
  print(data16.buffer.asUint16List());
}

which gives you:

[1, 2, 3, 4, 5, 6, 0] // List
[1, 2, 3, 4, 5, 6, 256] // List
7
14
[1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 0, 1] // View
[1, 2, 3, 4, 5, 6, 256] // View

This means: ByteBuffer is some abstract class which gives you the interface and basic functionality and you can get views from a buffer to access the data as you need it.

1
votes

The buffer accessed by 'data.buffer' is an internal buffer backing the Uint8List view referenced by 'data'. The transmitted payload -- what I want to wrap with the ByteData view -- is offset by some number of bytes into this buffer. The 'offset' of the payload can be determined through the 'offsetInBytes' property of the 'data' object. The following changes to decode() make it possible to use the ByteData API to decode various fragments of the payload byte array:

int offset = data.offsetInBytes; // sets offset to the starting position of the payload
int msgLength = bd.getInt32(offset);
double somethingElse = bd.getFloat64(offset+4);
// etc.