Skip to content

Access Uint8List as a Struct #926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
b0bh00d opened this issue Sep 2, 2023 · 3 comments
Open

Access Uint8List as a Struct #926

b0bh00d opened this issue Sep 2, 2023 · 3 comments

Comments

@b0bh00d
Copy link

b0bh00d commented Sep 2, 2023

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
Copy link
Author

b0bh00d commented Sep 3, 2023

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
Copy link
Collaborator

dcharkes commented Sep 4, 2023

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
Copy link
Author

b0bh00d commented Sep 5, 2023

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.

@dcharkes dcharkes transferred this issue from dart-archive/ffi Jan 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants