Update bits in [u8; 1500]


#1

Hello,

I am trying to build streaming WebRTC plugin for Janus Gateway.
The Janus Gateway itself is written in C and I would like to write the plugin in Rust.
So, my Rust code is going to interact with C.

I need to work with RTP. I got a test stream generated with ffmpeg.
The whole idea is as follows:
ffmpeg writes stream to a socket -> the plugin reads from the socket -> the plugin relays a data to Janus Gateway which passes it to WebRTC peer.

The problem is that I need to edit the data before relaying it to C code.

I got C plugin which works just fine. It looks like:

typedef struct rtp_header
{
	uint16_t version:2;
	uint16_t padding:1;
	uint16_t extension:1;
	uint16_t csrccount:4;
	uint16_t markerbit:1;
	uint16_t type:7;
	uint16_t seq_number;
	uint32_t timestamp;
	uint32_t ssrc;
	uint32_t csrc[16];
} rtp_header;

char buffer[1500];
bytes = recvfrom(audio_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
rtp_header *rtp = (rtp_header *)buffer;
rtp->type = 100; // this is an important thing to do
// Then I should just pass buffer to relay-function.

The important thing is that I should set type before passing the data.
In C this is done pretty easy. We just treat the data as pointer to rtp_header and manipulate with struct’s fields whatever we like.

I wonder how could I do the same thing in Rust?
For now I’ve just managed to poll on socket and read from it

// polling ...
let mut buf: [u8; 1500] = [0; 1500];
let (number_of_bytes, src_addr) = socket.recv_from(&mut buf).expect("Didn't receive data");

So, now I can pass buf to C relay-function but I don’t understand how could I edit the content of buf?

As you can see rtp_header use bitfields. As I understand Rust doesn’t support bitfields for structs.
I found bitfield crate which seems to solve the issue.
I created a struct describing an RTP header and tried to use it like:

bitfield!{
    struct RtpHeader(MSB0 [u8]);
    impl Debug;
    u8;
    get_version, _: 1, 0;
    get_padding, _: 2, 2;
    get_extension, _: 3, 3;
    get_csrc, _: 7, 4;
    get_marker, set_marker: 8, 8;
    get_payload_type, set_payload_type: 15, 9;
}
...
let mut header = RtpHeader(buf);
header.set_payload_type(100);

but here another problem comes in.

buf is an array with size of 1500. I don’t remember exactly but I read somewhere that Rust has some kind of special behaviour for arrays with size less than 32.
I got an error

    = note: the method `set_payload_type` exists but the following trait bounds were not satisfied:
            `[u8; 1500] : std::convert::AsRef<[u8]>`
            `[u8; 1500] : std::convert::AsMut<[u8]>`

What is the way to solve this issue in Rust?
How can I edit the content of buf before passing it to C function?

Thank you.


#2

You can use #[repr(C)] on Rust struct with the same fields as rtp_header and then use unsafe mem::transmute() on your array to change its type. transmute will check both types size equality and this is it, everything else is up to you, so if not careful it can fail horribly for various reasons.


#3

Thank you very much @newpavlov.
I know abour these things but I am puzzled to combine them.
I will try once again.


#4

@newpavlov,

I have defined the struct like

#[repr(C)]
#[derive(Debug)]
struct rtp_header {
    version: u8,
    padding: u8,
    extension: u8,
    csrccount: u8,
    markerbit: u8,
    type_: u8,
    seq_number: u16,
    timestamp: u32,
    ssrc: u32,
    // csrc: [u32; 16],
    // _unused: [u8; 1488],
}

and then used it like

unsafe {
    let header: rtp_header = std::ptr::read(buf.as_ptr() as *const _);
    println!("{:?}", header);
}

according to https://stackoverflow.com/a/42508686/3475678.
It seems to work like transmute, doesn’t?

But it produces wrong result (as I understand version should be equal to 2)

rtp_header { version: 128, padding: 225, extension: 13, csrccount: 145, markerbit: 69, type_: 225, seq_number: 61635, timestamp: 4288789725, ssrc: 951430904 }

I didn’t tell anywhere that version should be just 2 bits not an entire byte.
Shouldn’t I specify it somehow?


#5

Ah, sorry, missed bits part. Try to use let mut header = RtpHeader(&buf[..]); in the code from OP.

I forgot that support for bit fields is still not here


#6

Yes, the bits part is totally nightmare part.
I wonder if there is a solution at all.

With RtpHeader(&buf[..]) it gives me

    = note: the method `set_payload_type` exists but the following trait bounds were not satisfied:
            `&[u8] : std::convert::AsMut<[u8]>`

Is there any way to convert [u8; 1500] to [u8]?


#7

What you also could do is to do some wrapper functions on the C side that sets the data in the struct the way you want it. So you could just pass a pointer to the data and have the C side fill it out and then you can also have a function(s) on the C side to extract out the info that you want and have a Rust wrapper around that.

Might not be the most elegant solution but then you would treat the rtp_header struct more of a implementation detail on the C side.


#8

It requires buffer to be mutable (AsMut docs), so you need to pass RtpHeader(&mut buf[..]). (missed mut part too…)


#9

@emoon thanks, what is exactly what I did just to ensure it would work. Indeed, it is not very desired to write Rust plugin for C code and then fallback to C again (but it works well :slight_smile: ).


#10

@newpavlov thanks once again, I didn’t think mutability caused the error.

So, I end up with

let mut header = RtpHeader(&mut buf[..]);
header.set_payload_type(111);

and then to pass it like char * I use

header.0.as_mut_ptr() as *mut c_char

But the whole thing looks very much scary and dirty :slight_smile:


#11

Ah! Yeah I agree :slight_smile: