Mark Tomlin
Mark Tomlin

Reputation: 8943

Reading packets of data into structs in Rust

1. Summery of the problem.

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!?

Upvotes: 3

Views: 3059

Answers (1)

justinas
justinas

Reputation: 6867

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.

Upvotes: 4

Related Questions