Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading packets of data into structs in Rust

Tags:

rust

1. Summery of the problem.

  • I'm trying to read UDP packet data from a socket into a Struct in Rust.
  • Most of my googling has pointed me in the direction of external crates to do the parsing. That seems over kill and I would think that there must be a way using standard language features to "easily" read binary data into a struct without using anything that is unsafe. I would also like to use this for embedded platforms later on, so code size actually matters to me. (The minimum reproducible example below will use the std library so we can all start from a high point.)

2. I've tried the following ...

First of all, this is the php script that I'm using to send UDP packets to my program for testing.

<?php

$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$pack = pack('C*', 4, 3, 1, 0);

socket_sendto($sock, $pack, strlen($pack), 0, '127.0.0.1', 1337);
socket_close($sock);

Super simple, pack some binary and send it over the wire to my program. Everything is a known unique value so we can ensure that information is being read into the correct location in the struct.

#![allow(non_snake_case)]

use std::net::UdpSocket;
use std::mem::size_of;

#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
struct ISP_TINY {
    Size: u8, // Always 4
    Type: u8, // Always ISP_TINY = 3
    ReqI: u8, // 0 Unless return of a request for information.
    SubT: u8, // Sub Type from the TINY_ enumeration.
}

fn main() -> std::io::Result<()>
{
    // Some output so that we know the program has actally started.
    println!("Running ...");

    // Here we bind to the UDP socket, but the question mark allows us to return early if there is an error and end the program then and there.
    let socket = UdpSocket::bind("0.0.0.0:1337")?;

    // We'll read into this buffer space, but it has to be as big as or bigger than the largest packet your program can receive, otherwise it will cut off the rest of the data.
    let mut buf = [0; 256];
    loop
    {
        // recv_from blocks, and will wait (in sleep) until we get a packet to our bound socket.
        let (bytes, socketAddress) = socket.recv_from(&mut buf)?;

        // Once we get a packet, read from our buffer upto the packet length in bytes.
        let packet = &mut buf[..bytes];

        // Check that it's of the size we understand and want.
        if bytes != size_of::<ISP_TINY>()
        {
            println!("Got packet of size {} need packet of size {}", bytes, size_of::<ISP_TINY>());
            // Here we implicitly discard a packet we don't like and contiune the loop.
            continue;
        }

        // When we get a packet we want, we print it's contents, it's size and where it came from.
        println!("Packet Recv {:#?} of size {} from {}", packet, bytes, socketAddress);
    }
}

3. Show some code.

I've gone though a tremendous numbers of implementations to make rust happy. Over the past 24 hours, I think I've done about 10 different designs. None of them seems to have made it happy with the solutions that I've come up with.

This doesn't look like it would be a hard problem. I have the data, I can just slice into it? Right?

        let tiny = ISP_TINY {
            Size: packet[0 .. 1],
            Type: packet[1 .. 2],
            ReqI: packet[2 .. 3],
            SubT: packet[3 .. 4]
        };

Wrong

error[E0308]: mismatched types
  --> src/main.rs:42:19
   |
42 |             Size: packet[0 .. 1],
   |                   ^^^^^^^^^^^^^^ expected `u8`, found slice `[u8]`

error[E0308]: mismatched types
  --> src/main.rs:43:19
   |
43 |             Type: packet[1 .. 2],
   |                   ^^^^^^^^^^^^^^ expected `u8`, found slice `[u8]`

error[E0308]: mismatched types
  --> src/main.rs:44:19
   |
44 |             ReqI: packet[2 .. 3],
   |                   ^^^^^^^^^^^^^^ expected `u8`, found slice `[u8]`

error[E0308]: mismatched types
  --> src/main.rs:45:19
   |
45 |             SubT: packet[3 .. 4]
   |                   ^^^^^^^^^^^^^^ expected `u8`, found slice `[u8]`

error: aborting due to 4 previous errors

So I said to myself, maybe I need to create an implementation ...

impl ISP_TINY
{
    fn from(&self, packet: &[u8])
    {
        self.Size: packet[0 .. 1],
        self.Type: packet[1 .. 2],
        self.ReqI: packet[2 .. 3],
        self.SubT: packet[3 .. 4]
    }
}

It doesn't like that either.

error: expected one of `!`, `(`, `::`, `;`, `<`, or `}`, found `[`
  --> src/main.rs:19:26
   |
19 |         self.Size: packet[0 .. 1],
   |                  -       ^ expected one of `!`, `(`, `::`, `;`, `<`, or `}`
   |                  |
   |                  tried to parse a type due to this

error: aborting due to previous error

These are the two of the 10 that I thought would be the sensible solutions. But they don't work and I'm not sure how to get around this problem within the Rust language. How do you read raw binary data from packets into structs!?

like image 202
Mark Tomlin Avatar asked Mar 14 '26 15:03

Mark Tomlin


1 Answers

Just index by an integer to get a single byte, e.g. packet[0] instead of packet[0..1]. This will give you an u8 instead of a slice of length 1.

For converting an array of bytes to a larger integer, e.g. u16, i64, etc., one can use functions like from_le_bytes, from_be_bytes and from_ne_bytes defined on the native integers.

like image 53
justinas Avatar answered Mar 17 '26 18:03

justinas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!