Skip to content
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

AMF packets (implementation) #9

Open
bugproof opened this issue Mar 23, 2020 · 5 comments
Open

AMF packets (implementation) #9

bugproof opened this issue Mar 23, 2020 · 5 comments

Comments

@bugproof
Copy link

bugproof commented Mar 23, 2020

Hey I implemented serialization and deserialization of AMF packets

It's my first time writing something like that in Rust so correct me if I did something horrible in code.

Feel free to do whatever you want with it, include in amf crate maybe as optional feature or if you wish I can create amf_packet crate.

I tested it and it seems to work fine

// amf_packet.rs

use std::{io};
use std::convert::{TryFrom, TryInto};
use amf::{Version, Amf0Value};
use amf::amf0::{Value, array, string, number};
extern crate byteorder;
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian, LittleEndian};
use std::fs::File;
use std::io::Read;
use std::string::FromUtf8Error;
use std::borrow::BorrowMut;

pub struct AmfHeader {
    pub name: String,
    pub must_understand: bool,
    pub content: Value,
}

pub struct AmfMessage {
    pub target: String,
    pub response: String,
    pub content: Value,
}

pub struct AmfPacket {
    pub version: Version,
    pub headers: Vec<AmfHeader>,
    pub messages: Vec<AmfMessage>,
}

trait ReadBytesExt2: io::Read {
    #[inline]
    fn read_bytes(&mut self, size: usize) -> Vec<u8> {
        let mut buf = vec![0; size];
        self.read_exact(&mut buf).unwrap();
        buf
    }

    #[inline]
    fn read_utf8_string(&mut self, length: usize) -> io::Result<String> {
        Ok(String::from_utf8(self.read_bytes(length)).unwrap())
    }
}
impl<R: io::Read + ?Sized> ReadBytesExt2 for R {}

impl AmfPacket {
    pub fn new(version: Version) -> Self {
        AmfPacket {version, headers: Vec::new(), messages: Vec::new()}
    }

    pub fn read_from<R>(mut reader: R) -> io::Result<Self>
        where
            R: io::Read,
    {
        let version = reader.read_u16::<BigEndian>()?;
        let mut headers = Vec::new();
        let header_count = reader.read_u16::<BigEndian>()?;
        for _ in 0..header_count {
            let header_name_length = reader.read_u16::<BigEndian>()?;
            let header_name = reader.read_utf8_string(header_name_length as usize)?;
            let must_understand = reader.read_u8()? != 0;
            let _header_length = reader.read_i32::<BigEndian>()?;
            let content = Value::read_from(&mut reader).unwrap();
            headers.push(AmfHeader {name: header_name, must_understand, content });
        }

        let mut messages = Vec::new();
        let message_count = reader.read_u16::<BigEndian>()?;
        for _ in 0..message_count {
            let target_length = reader.read_u16::<BigEndian>()?;
            let target = reader.read_utf8_string(target_length as usize)?;
            let response_length = reader.read_u16::<BigEndian>()?;
            let response = reader.read_utf8_string(response_length as usize)?;
            let _message_length = reader.read_i32::<BigEndian>()?;
            let content = Value::read_from(&mut reader).unwrap();
            messages.push(AmfMessage {target, response, content});
        }

        Ok(AmfPacket { version: match version {
            0 => Version::Amf0,
            3 => Version::Amf3,
            _ => panic!("Unknown version! Probably not AMF packet: {}", version),
        }, headers, messages })
    }

    pub fn write_to<W>(&self, mut writer: W) -> io::Result<()>
        where
            W: io::Write,
    {
        writer.write_u16::<BigEndian>(match self.version {
            Version::Amf0 => 0,
            Version::Amf3 => 3
        })?;
        writer.write_u16::<BigEndian>(self.headers.len() as u16)?;

        for header in &self.headers {
            writer.write_u16::<BigEndian>(header.name.len() as u16)?;
            writer.write_all(header.name.as_bytes())?;
            writer.write_u8(header.must_understand as u8)?;
            writer.write_i32::<BigEndian>(-1)?; // https://www.adobe.com/content/dam/acom/en/devnet/pdf/amf0-file-format-specification.pdf -1 can be specified
            header.content.write_to(&mut writer)?;
        }

        writer.write_u16::<BigEndian>(self.messages.len() as u16)?;

        for message in &self.messages {
            writer.write_u16::<BigEndian>(message.target.len() as u16)?;
            writer.write_all(message.target.as_bytes())?;
            writer.write_u16::<BigEndian>(message.response.len() as u16)?;
            writer.write_all(message.response.as_bytes())?;
            writer.write_i32::<BigEndian>(-1)?;
            message.content.write_to(&mut writer)?;
        }

        Ok(())
    }
}
@bugproof bugproof changed the title AMF packets AMF packets (implementation) Mar 24, 2020
@sile
Copy link
Owner

sile commented Mar 29, 2020

Thank you for creating this issue.
It's welcome to create PR to introduce this feature to amf crate.
I think that it would be good to create amf/packet module and locate the above structs under the module.
If you could create a PR, I would review the further detail on the PR.

@bugproof
Copy link
Author

bugproof commented Mar 29, 2020

The only thing that stops it from working sometimes is lack of IExternalizable support. E.g. flex.messaging.io.ArrayCollection are pretty common in responses. And I still don't know how to handle encoding correctly. AMF packets seem to always use amf0 format even despite the version is 3. Maybe it's used only for encoding but that doesn't make much sense to me and documentation is lacking

@sile
Copy link
Owner

sile commented Mar 30, 2020

The only thing that stops it from working sometimes is lack of IExternalizable support. E.g. flex.messaging.io.ArrayCollection are pretty common in responses.

Hmm, in my use case (RTMP implementation), I didn't need the handling of IExternalizable, so I don't know how to support it...

AMF packets seem to always use amf0 format even despite the version is 3.

I don't know why, but I agree this.

@bugproof
Copy link
Author

bugproof commented Apr 4, 2020

AMF packets depend a lot on IExternalizable I think so it would need to be supported first. I don't have time for that now. We would need some Externalizable trait I think and some mapping/factory. flex.messaging.io.ArrayCollection is the most commonly used and under the hood it's just an array and it can be treated as an array during deserialization

@sile
Copy link
Owner

sile commented Apr 5, 2020

Thank you for your information. If you have time in the future, the contribution will be welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants