dart-lang / native

Dart packages related to FFI and native assets bundling.
BSD 3-Clause "New" or "Revised" License
155 stars 43 forks source link

Access Uint8List as a Struct #926

Open b0bh00d opened 1 year ago

b0bh00d commented 1 year ago

Apologies if this is somehow self-evident (or if this is not the appropriate place to ask such questions), but I could not find a single example that demonstrates this.

I have received a UDP datagram from a desktop C++ application that corresponds to the following C struct:

struct Packet
{
   int magic;
   int sender;
   int action;
   int payload_size;
   uint8_t payload[1];
}

I have defined a Struct to mirror it in Dart:

final class Packet extends Struct {
  @Int32()
  external int magic;

  @Int32()
  external int sender;

  @Int32()
  external int action;

  @Int32()
  external int payload_size;

  external Pointer<Uint8> payload;
}

Can I cast the Uint8List datagram binary data into an instance of this Struct (e.g., Pointer<Packet>)? Or should I instead be using more primitive functions that crawl offsets, like:

var buffer = new Uint8List(8).buffer;
var bytes = new ByteData.view(buffer);
bytes.getUint16(offset);

Thanks in advance for any assistance.

b0bh00d commented 1 year ago

I've iterated my way to what appears to be a functional approach that works right now:

  final buffer = datagram.buffer;
  final bytes = ByteData.view(buffer);

  final int magic = bytes.getInt32(0, Endian.little);
  final int sender = bytes.getInt32(4, Endian.little);
  final Action action = Action.values[bytes.getInt32(8, Endian.little)];
  final int payloadSize = bytes.getInt32(12, Endian.little);

  final payloadBuffer = bytes.buffer;
  final payload = payloadBuffer.asUint8List(16, payloadSize);

However, I'll hold this open for somebody to tell me if this is the best I can do. Thanks.

dcharkes commented 1 year ago

How is the memory layout of the datagram defined? Does it correspond to the memory layout of a struct in C? (For example, if you have a single byte field and then a large int, does it insert padding in between?)

If the datagram follows the C ABI struct memory layout, we could consider making a constructor on Struct subtypes that takes a typed data as argument.

If the datagram does not follow the C ABI struct memory layout, the solution that you have come up with is the best we can do I believe, because you have to reason about the offsets according to what the sender defined as offsets and padding.

b0bh00d commented 1 year ago

Yes, the data is compliant with the C ABI. The struct is 17 bytes in size, but the data being received is 20 (plus the payload), so the struct on the C/C++ side is indeed begin padded.

Using the ByteData.view() approach above, I'm functional, but it would be a nice language feature to able to define a Struct and then overlay it onto binary data (as one can in C/C++). I realized after posting that it would probably not be simple, as I had to specify Endian.lilttle to properly decode the integers, and that would somehow have to be designated/handled in the Struct.

Since I have a functional work-around, I guess this can be considered more of a feature request. :)

Thanks for a great language! I really love Dart/Flutter.