klingtnet / rosc

An OSC library for Rust.
Apache License 2.0
173 stars 25 forks source link

Send Osc packet with TCP #45

Closed YievCkim closed 1 year ago

YievCkim commented 1 year ago

Hello,

I am trying to use rosc from tokio. My first step is to decode and display incoming osc messages. I have made the server:

use rosc::decoder;
use rosc::{OscMessage, OscPacket};
use tokio::io::AsyncReadExt;
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("start");
    let listener = TcpListener::bind("0.0.0.0:9000").await?;
    println!("listening");
    loop {
        match listener.accept().await {
            Ok((mut socket, addr)) => {
                println!("new client: {:?}", addr);

                println!("ready");
                tokio::spawn(async move {
                    let mut buf = [0; 1024];

                    // In a loop, read data from the socket and write the data back.
                    loop {
                        let _ = match socket.read(&mut buf).await {
                            Ok(n) if n == 0 => {
                                eprintln!("end tran");
                                return;
                            }
                            Ok(n) => {
                                println!("{:?}", &buf[..n]);
                                println!("read {} bytes", n);
                                match decoder::decode_tcp(&buf[..n]) {
                                    Ok((_, payload)) => {
                                        println!("{:?}", payload);
                                        return;
                                    }
                                    _ => {
                                    eprintln!("decoding error");
                                    return;
                                    }
                                }
                            }
                            Err(e) => {
                                eprintln!("failed to read from socket; err = {:?}", e);
                                return;
                            }
                        };
                    }
                });
            }
            Err(e) => eprintln!("could not get client: {:?}", e),
        }
    }
}

and a simple client inspired from documentation:

use rosc::encoder;
use rosc::{OscMessage, OscPacket, OscType};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "127.0.0.1:9000";
    let mut stream = TcpStream::connect(addr).await?;
    let osc_packet = OscPacket::Message(OscMessage {
        addr: "/greet/me".to_string(),
        args: vec![OscType::String("hi!".to_string())],
    });
    if let Ok(buf) = encoder::encode(&osc_packet) {
        println!("send packet");
        println!("{:?}", buf);
        stream.write_all(&buf).await?;
        println!("transmitted");
    }
    Ok(())
}

When the server receives the packet it seems to fail decoding it, and put everything in the left first tuple element:

([47, 103, 114, 101, 101, 116, 47, 109, 101, 0, 0, 0, 44, 115, 0, 0, 104, 105, 33, 0], None)

I have made a little program to see what happens:

use rosc::{encoder,decoder};
use rosc::{OscMessage, OscPacket, OscType};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let osc_packet = OscPacket::Message(OscMessage {
        addr: "/greet/me".to_string(),
        args: vec![OscType::String("hi!".to_string())],
    });
    if let Ok(buf) = encoder::encode(&osc_packet) {
        println!("{:?}", buf);
        let payload = decoder::decode_tcp(&buf)?;
        println!("{:?}", payload);
    }
    Ok(())
}

got this output:

mik@debian:~/hello$ cargo run  --example test_decode
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/examples/test_decode`
[47, 103, 114, 101, 101, 116, 47, 109, 101, 0, 0, 0, 44, 115, 0, 0, 104, 105, 33, 0]
([47, 103, 114, 101, 101, 116, 47, 109, 101, 0, 0, 0, 44, 115, 0, 0, 104, 105, 33, 0], None)
mik@debian:~/hello$

I guess I missuse the api. I tried to find something to decode directly from a buffer in the API without succeed.

YievCkim commented 1 year ago

It works using decoder::decode_udp. Is it a feature ? Why not a decode function protocol agnostic ?

klingtnet commented 1 year ago

Hey @MikHulk !

The naming is a bit misleading, decode_tcp is actually protocol agnostic and just takes a byte slice to decode.

Other than that, I'm not sure at the moment why decode_tcp fails in the server. Which rosc version are you using?

klingtnet commented 1 year ago

I tried to reproduce the issue with your example files, but the server is not compiling:


$ cargo build --bin server
   Compiling example v0.1.0 (/private/var/folders/mc/3ssj7_c51dv76d6ll79s5l3w0000gn/T/tmp.pmm1mYUl/example)
error[E0432]: unresolved import `tokio::io::AsyncReadExt`
 --> src/server/server.rs:3:5
  |
3 | use tokio::io::AsyncReadExt;
  |     ^^^^^^^^^^^------------
  |     |          |
  |     |          help: a similar name exists in the module: `AsyncRead`
  |     no `AsyncReadExt` in `io`

error[E0432]: unresolved import `tokio::net::TcpListener`
 --> src/server/server.rs:4:5
  |
4 | use tokio::net::TcpListener;
  |     ^^^^^^^^^^^^^^^^^^^^^^^ no `TcpListener` in `net`
  |
help: consider importing this struct instead
  |
4 | use std::net::TcpListener;
  |     ~~~~~~~~~~~~~~~~~~~~~

error[E0433]: failed to resolve: could not find `main` in `tokio`
 --> src/server/server.rs:6:10
  |
6 | #[tokio::main]
  |          ^^^^ could not find `main` in `tokio`

error[E0425]: cannot find function `spawn` in crate `tokio`
  --> src/server/server.rs:17:24
   |
17 |                 tokio::spawn(async move {
   |                        ^^^^^ not found in `tokio`
   |
help: consider importing this function
   |
1  | use std::thread::spawn;
   |
help: if you import `spawn`, refer to it directly
   |
17 -                 tokio::spawn(async move {
17 +                 spawn(async move {
   |

warning: unused imports: `OscMessage`, `OscPacket`
 --> src/server/server.rs:2:12
  |
2 | use rosc::{OscMessage, OscPacket};
  |            ^^^^^^^^^^  ^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

error[E0752]: `main` function is not allowed to be `async`
 --> src/server/server.rs:7:1
  |
7 | async fn main() -> Result<(), Box<dyn std::error::Error>> {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `main` function is not allowed to be `async`

Some errors have detailed explanations: E0425, E0432, E0433, E0752.
For more information about an error, try `rustc --explain E0425`.
warning: `example` (bin "server") generated 1 warning
error: could not compile `example` due to 5 previous errors; 1 warning emitted
```
YievCkim commented 1 year ago

Hi @klingtnet

Sorry I should start with that this is my Cargo.toml:

[package]
name = "hello"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tokio = { version = "1.27.0", features = ["full"] }
rosc="0.10.0"

I looked into decode_tcp tests, ans it seems there is some prefdix added in order to build something onto decode_tcp can work.

klingtnet commented 1 year ago

I remembered what the problem is, if you decode OSC messages from a TCP stream then the OSC specification expects that the size of the message is the first element in the byte-stream:

In a stream-based protocol such as TCP, the stream should begin with an int32 giving the size of the first packet, followed by the contents of the first packet, followed by the size of the second packet, etc.

That's why decode_tcp has failed in your server, and why it's actually named decode_tcp and not just decode. Of course, the doc string could be a bit more explicit and explain this. I might add a note about this condition to the docs.

YievCkim commented 1 year ago

this is my rustc version:

mik@debian:~/hello$ cargo --version
cargo 1.69.0 (6e9a83356 2023-04-12)
mik@debian:~/hello$ rustc --version
rustc 1.69.0 (84c898d65 2023-04-16)
mik@debian:~/hello$ 

What was your setup when you try ?

klingtnet commented 1 year ago

It's

rustc 1.71.0-nightly (b628260df 2023-04-22

however, it worked with tokio = { version = "1.27.0", features = ["full"] }.

As explained in my previous message, this is not an issue with rosc. So I'll close this issue if there's no objection from your side.

YievCkim commented 1 year ago

I remembered what the problem is, if you decode OSC messages from a TCP stream then the OSC specification expects that the size of the message is the first element in the byte-stream:

In a stream-based protocol such as TCP, the stream should begin with an int32 giving the size of the first packet, followed by the contents of the first packet, followed by the size of the second packet, etc.

That's why decode_tcp has failed in your server, and why it's actually named decode_tcp and not just decode. Of course, the doc string could be a bit more explicit and explain this. I might add a note about this condition to the docs.

humm ok. But I have a tcp connection here. If I follow the specification I should set this byte. Is there something to encode tcp packet ? I think I can implement that on my side. Just to be sure.

I didn't read extensively OSC specification, I am reading it right now. I am not sure if this byte is something from TCP or OSC.

YievCkim commented 1 year ago

no problem. I am looking specification.

klingtnet commented 1 year ago

Is there something to encode tcp packet ?

There's no method that does that for you, but it might be a good idea to add some utility function like encode_tcp to the API of rosc.

YievCkim commented 1 year ago

yes I have a local repo. I could do that.

For documentation this is the proper way to send Osc packet with tcp:

use rosc::encoder;
use rosc::{OscMessage, OscPacket, OscType};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "127.0.0.1:9000";
    let mut stream = TcpStream::connect(addr).await?;
    let osc_packet = OscPacket::Message(OscMessage {
        addr: "/greet/me".to_string(),
        args: vec![OscType::String("hi!".to_string())],
    });
    if let Ok(buf) = encoder::encode(&osc_packet) {
        println!("send packet");
        println!("{:?}", buf);
        stream.write_all(&(buf.len() as u32).to_be_bytes()).await?;
        stream.write_all(&buf).await?;
        println!("transmitted");
    }
    Ok(())
}
YievCkim commented 1 year ago

I'll try an implementation this weekend. Thank you !