Giter Site home page Giter Site logo

wtransport's Introduction

WTransport Logo

Documentation Crates.io CI Chat Zulip chat

WTransport

WebTransport protocol, pure-rust, async-friendly.

Introduction

WebTransport is a new protocol being developed to enable low-latency, bidirectional communication between clients and servers over the web. It aims to address the limitations of existing protocols like HTTP and WebSocket by offering a more efficient and flexible transport layer.

Benefits of WebTransport

  • ๐Ÿš€ Low latency: WebTransport is designed to minimize latency, making it suitable for real-time applications such as gaming, video streaming, and collaborative editing.
  • ๐Ÿ”„ Bidirectional communication: WebTransport allows simultaneous data exchange between the client and server, enabling efficient back-and-forth communication without the need for multiple requests.
  • ๐Ÿ”€ Multiplexing: With WebTransport, multiple streams can be multiplexed over a single connection, reducing overhead and improving performance.
  • ๐Ÿ”’ Security: WebTransport benefits from the security features provided by the web platform, including transport encryption and same-origin policy.
  • ๐ŸŒ Native Browser Support: WebTransport is natively supported in modern web browsers, ensuring seamless integration and enhanced performance for web applications.

Check Library Documentation

Notes

Please be aware that WebTransport is still a draft and not yet standardized. The WTransport library, while functional, is not considered completely production-ready. It should be used with caution and may undergo changes as the WebTransport specification evolves.

Simple API

Server Client
#[tokio::main]
async fn main() -> Result<()> {
    let config = ServerConfig::builder()
        .with_bind_default(4433)
        .with_identity(&identity)
        .build();

    let connection = Endpoint::server(config)?
        .accept()
        .await     // Awaits connection
        .await?    // Awaits session request
        .accept()  // Accepts request
        .await?;   // Awaits ready session

    let stream = connection.accept_bi().await?;
    // ...
}
#[tokio::main]
async fn main() -> Result<()> {
    let config = ClientConfig::default();

    let connection = Endpoint::client(config)?
        .connect("https://[::1]:4433")
        .await?;

    let stream = connection.open_bi().await?.await?;
    // ...
}

Browser Integration

WebTransport is supported in modern browsers, enhancing the capabilities of web applications.

For instance, you can create a native browser WebTransport client connecting to a Rust server using the following JavaScript code:

// Create a WebTransport instance connecting to the Rust server
let transport = new WebTransport('https://[::1]:4433');
await transport.ready;

// Create a bidirectional stream
let stream = await transport.createBidirectionalStream();

// Send data from the client to the server
await stream.writable.getWriter().write(new TextEncoder().encode("hello"));

// Read data reply from the server
let data = await stream.readable.getReader().read();
console.log(data);

Check out the W3C WebTransport API documentation for more details and to explore the full capabilities of WebTransport in the browser.

Getting Started

Clone the Repository

git clone https://github.com/BiagioFesta/wtransport.git
cd wtransport/

Run Full Example

The examples/full.rs is a minimal but complete server example that demonstrates the usage of WebTransport.

You can run this example using Cargo, Rust's package manager, with the following command:

cargo run --example full

This example initiates an echo WebTransport server that can receive messages. It also includes an integrated HTTP server.

Open Google Chrome and navigate to the page http://127.0.0.1:8080.

Examples

Other Languages

WTransport has bindings for the following languages:

wtransport's People

Contributors

aecsocket avatar biagiofesta avatar dependabot[bot] avatar hhrsscc avatar jensl avatar justsomedude301 avatar mozgiii avatar rofferom avatar rom1v avatar tausifcreates avatar waywardmonkeys avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

wtransport's Issues

Error displays should be lowercase

Nitpick but it makes the rest of the error chain look strange.

The Rust API docs state:

The error message given by the Display representation of an error type should be lowercase without trailing punctuation, and typically concise.

Currently, this crate doesn't conform to this style, but this shouldn't be too big of a change.

I.e.

/// An enumeration representing various errors that can occur during a WebTransport client connecting.
#[derive(thiserror::Error, Debug)]
pub enum ConnectingError {
    /// URL provided for connection is not valid.
    #[error("Invalid URL: {0}")]
    InvalidUrl(String),

to

/// An enumeration representing various errors that can occur during a WebTransport client connecting.
#[derive(thiserror::Error, Debug)]
pub enum ConnectingError {
    /// URL provided for connection is not valid.
    #[error("invalid URL: {0}")]
    InvalidUrl(String),

Feature request: Expose the HTTP headers as a HashMap

It would be nice to have exposed the underlying HashMap of the Headers struct, instead of having to call session_request.get(header) or session_request.headers().get(header) for every header I'm interested in.
I tried accessing session_request.headers().0 but it's a private field.

Session ID of each connection from one host is not unique

Source Code Reference: https://gist.github.com/JustSomeDude301/9803efa298a46b6b55015f69a98b0e01

Filing this moreso for information. Is this behavior intentional?

I am using a concurrent hashmap implementation (DashMap) for storing the Connection objects. Currently the Key I use for each connection is the remote address of the connection. But if this is intentional and each Connection with an identical SessionId should be treated as one pooled "Connection" I may have to rethink my approach.

When making multiple connections from localhost each one has a unique remote address, however the SessionId is identical:

Connection 1:

Incoming request
 Authority: localhost:4433
 Path: /
 Headers: Headers(
    {
        "origin": "https://webtransport.day",
        ":path": "/",
        ":protocol": "webtransport",
        "sec-webtransport-http3-draft02": "1",
        ":scheme": "https",
        ":method": "CONNECT",
        ":authority": "localhost:4433",
    },
)
Added new connection from: [::1]:51920
Received (datagram): HEY from client, remote addr: [::1]:51920, session id: 0

Connection 2:

Incoming request
 Authority: localhost:4433
 Path: /
 Headers: Headers(
    {
        "origin": "https://webtransport.day",
        ":authority": "localhost:4433",
        ":path": "/",
        "sec-webtransport-http3-draft02": "1",
        ":scheme": "https",
        ":method": "CONNECT",
        ":protocol": "webtransport",
    },
)
Added new connection from: [::1]:53210
Received (datagram): HELLOW from client, remote addr: [::1]:53210, session id: 0

The IETF Draft says:

WebTransport sessions are initiated inside a given HTTP/3 connection by the client, who sends an extended CONNECT request [RFC8441]. If the server accepts the request, an WebTransport session is established. The resulting stream will be further referred to as a CONNECT stream, and its stream ID is used to uniquely identify a given WebTransport session within the connection. The ID of the CONNECT stream that established a given WebTransport session will be further referred to as a Session ID.

The way I am interpreting this is that two unique Connections from the same host will have unique SessionId's, but feel free to correct my understanding

BTW I love the library, I am using it to make a Datagram server framework intended for porting UDP Multiplayer Netcode libraries

Feature(s) for rcgen and ring

The version added rcgen and ring as dependencies, I'd like to avoid pulling them into my codebase.

I propose that we make them optional and add a default-on feature to enable them and the related functionality.

IncomingSession & SessionRequest must implement Send

As title says.

tokio::spawn(async {
    server().await;
});
async fn server() -> Result<()> {
    // ...

    let server = Endpoint::server(config)?;
    loop {
        let incoming_session = server.accept().await;
        let session_request = incoming_session.await?;
        let connection = session_request.accept().await?;
        tokio::spawn(server_connection(connection));
    }
}
error: future cannot be sent between threads safely
   --> server-shuttle/src/main.rs:24:18
    |
24  |       tokio::spawn(async {
    |  __________________^
25  | |         start_web_transport().await;
26  | |     });
    | |_____^ future created by async block is not `Send`
    |
    = help: the trait `Send` is not implemented for `(dyn std::future::Future<Output = Result<wtransport::endpoint::SessionRequest, wtransport::error::ConnectionError>> + 'static)`
note: future is not `Send` as this value is used across an await
    |
25  |         let incoming_session = server.accept().await;
    |             ---------------- has type `wtransport::endpoint::IncomingSession` which is not `Send`
26  |         let session_request = incoming_session.await?;
    |                                               ^^^^^^ await occurs here, with `incoming_session` maybe used later
...
29  |     }
    |     - `incoming_session` is later dropped here

The `only_v6` flag of the `with_bind_address` method is not honoured

I have a ServerConfig builder that looks like this:

    let config = ServerConfig::builder()
        .with_bind_address(bind_address, false)
        .with_certificate(certificate)
        .keep_alive_interval(Some(Duration::from_secs(3)))
        .build();

So the only_v6 flag is false (side note: it's not good API design to pass boolean parameters to a function, because if I read that function without reading its documentation, what does false mean? It's better to use an enum in this case, but I digress).

The output of the command ss -ulpen is as follows:

UNCONN                    0                         0                                                     [::1]:4433                                                  [::]:*                        users
(("beam.smp",pid=7804,fd=20)) uid:1000 ino:54876 sk:9 cgroup:/user.slice/user-1000.slice/[email protected]/app.slice/app-org.gnome.Terminal.slice/vte-spawn-394db383-bbc8-4b17-adb2-a74b89adb521.scope v6only:1 <->

Note the v6only:1 part in the output.

Expose maxDatagramSize property

As title says.

This property is almost mandatory if larger packets are needed to be sent (like video frames) which might be larger than MTU. This will result in an error.

WebTransport Docs:
https://developer.mozilla.org/en-US/docs/Web/API/WebTransportDatagramDuplexStream/maxDatagramSize

This is why we need it:

/// Transmit `data` as an unreliable, unordered application datagram
///
/// Application datagrams are a low-level primitive. They may be lost or delivered out of order,
/// and `data` must both fit inside a single QUIC packet and be smaller than the maximum
/// dictated by the peer.
pub fn send_datagram(&self, data: Bytes) -> Result<(), SendDatagramError>

This is how to get max datagram size in quinn:

/// Compute the maximum size of datagrams that may passed to `send_datagram`
///
/// Returns `None` if datagrams are unsupported by the peer or disabled locally.
///
/// This may change over the lifetime of a connection according to variation in the path MTU
/// estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer's
/// limit is large this is guaranteed to be a little over a kilobyte at minimum.
///
/// Not necessarily the maximum size of received datagrams.
pub fn max_size(&self) -> Option<usize>

SSL_CERT_FILE door for malicious certificates

Currently, the ClientConfig is generally configured using the with_native_certs method. This method loads the platform's native certificate store, which contains Certificate Authorities (CAs) used for validating server certificates during the connection handshake.

Typically, platform CA stores are secured with system privileges, so an attacker might need to acquire root privileges to compromise certificate validation.

However, the current implementation of with_native_certs is backed by rustls-native-certs, which checks if the SSL_CERT_FILE environment variable is set. If it is set, certificates in PEM format are read from that file instead.

This means that it's possible to override the certificate validation with different CAs simply by setting the SSL_CERT_FILE environment variable. An attacker could potentially exploit this to alter the system environment without accessing system folders or requiring admin privileges.

However, SSL_CERT_FILE seems to be a valid configuration for OpenSSL as well: OpenSSL Documentation.

In any case, this library should either:

  • Explicitly declare this possibility in the documentation of the with_native_certs method so that the user is aware of it.
  • Avoid this potential attack vector and only load CAs from the native store, ignoring the environment variable.
    • In this case, it might be possible to add an additional method to explicitly specify the root store directory.

Protocol not available (os error 92) when using IPv4

I'm trying to implement the code sample described in #50 , so I'm using this code:

    let addrs_iter = (host, port).to_socket_addrs()?;

            let endpoints = addrs_iter
                .map(|bind_address| {
                    info!("bind_address: {:?}", bind_address);
                    info!("is_ipv4: {:?}", bind_address.is_ipv4());
                    info!("is_ipv6: {:?}", bind_address.is_ipv6());

                    let config = ServerConfig::builder()
                        .with_bind_address(bind_address, false)
                        .with_certificate(Certificate::load(&certfile, &keyfile)?)
                        .keep_alive_interval(Some(Duration::from_secs(3)))
                        .build();

                    Endpoint::server(config)
                })
                .collect::<Result<Vec<Endpoint<Server>>, std::io::Error>>()

host is set to "localhost" and port is set to 4433.

The IPv6 part binds fine, but the IPv4 part errors out with:

2023-09-23T07:41:03.618411Z  INFO wtransport_native: bind_address: 127.0.0.1:4433
2023-09-23T07:41:03.618422Z  INFO wtransport_native: is_ipv4: true
2023-09-23T07:41:03.618427Z  INFO wtransport_native: is_ipv6: false
2023-09-23T07:41:03.619096Z ERROR wtransport_native: "Protocol not available (os error 92)"

I tried changing the IPV6ONLY flag from false to true, but it didn't change anything.

I also tried to set host to ::1, and it works fine by binding only to IPv6 localhost, an I also tried to set host to 127.0.0.1, and it errors out with the same error.

My OS is Fedora 38 x86_64, if that could help.

btw. IPv4 localhost UDP binding is supported on my OS, the output of ss -ulpen also reports:

udp        UNCONN      0           0                   127.0.0.54:53                     0.0.0.0:*          uid:193 ino:23014 sk:1 cgroup:/system.slice/systemd-resolved.service <->               

Which has noting to do with wtransport, but shows that it's possible to bind an UDP socket to 127.0.0.1 on my system

My suggestions for APIs

I have a suggestion that the same stream should only be triggered once, and then the data transmitted through the stream will pass through OnStreamData. It is also recommended that each stream carry a streamId.

Does not work on Firefox 114

So, I used the latest git of wtransport, and built both the server and client examples.
For the server I generated the certificates with mkcert, and modified the server example code to point to the generated certificates.
If I run the server and client examples, everything works.
But if I try the server example, and on the client I use Firefox 114, and go to

https://webtransport.day/

and pass

https://[::1]:4433/

as the URL and click connect, the connection fails, and the server crashes with the error:

Waiting for incoming connection...
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ConnectionClosed(ConnectionClose(ConnectionClose { error_code: Code::crypto(2a), frame_type: None, reason: b"" }))', wtransport/examples/server.rs:22:63
stack backtrace:
   0:     0x559fa52a0dda - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h3fd184b2101eb13c
   1:     0x559fa52c421f - core::fmt::write::h51fa8e21bfac13ae
   2:     0x559fa529e045 - std::io::Write::write_fmt::hb170cc94d5e2f613
   3:     0x559fa52a0ba5 - std::sys_common::backtrace::print::h403826a4179d5670
   4:     0x559fa52a224e - std::panicking::default_hook::{{closure}}::ha8e7f4094a32fcab
   5:     0x559fa52a1ff5 - std::panicking::default_hook::hc86b583b1c4ca7a5
   6:     0x559fa52a27ae - std::panicking::rust_panic_with_hook::hc497c7954fcfd668
   7:     0x559fa52a26a9 - std::panicking::begin_panic_handler::{{closure}}::h7188665de380b804
   8:     0x559fa52a1246 - std::sys_common::backtrace::__rust_end_short_backtrace::hb744ac52f62fb536
   9:     0x559fa52a2402 - rust_begin_unwind
  10:     0x559fa4dd4d43 - core::panicking::panic_fmt::h5fbbc72c7fa660e4
  11:     0x559fa4dd52e3 - core::result::unwrap_failed::h86096f7f4e455f48
  12:     0x559fa4dd781e - core::result::Result<T,E>::unwrap::h127d240e5dbce379
                               at /builddir/build/BUILD/rustc-1.70.0-src/library/core/src/result.rs:1089:23
  13:     0x559fa4de171d - server::main::{{closure}}::h81fef686e2798d64
                               at /robba/tmp/wtransport/wtransport/examples/server.rs:22:26
  14:     0x559fa4ddfe14 - tokio::runtime::park::CachedParkThread::block_on::{{closure}}::h8e76271b240a8e96
                               at /home/fri/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/park.rs:283:63
  15:     0x559fa4ddfc40 - tokio::runtime::coop::with_budget::h5c4ee202f8c92a52
                               at /home/fri/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/coop.rs:107:5
  16:     0x559fa4ddfc40 - tokio::runtime::coop::budget::hdc06251f8845cd48
                               at /home/fri/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/coop.rs:73:5
  17:     0x559fa4ddfc40 - tokio::runtime::park::CachedParkThread::block_on::h9a64a77b1cd98e90
                               at /home/fri/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/park.rs:283:31
  18:     0x559fa4de3693 - tokio::runtime::context::BlockingRegionGuard::block_on::h55ee2199d53c0cbb
                               at /home/fri/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/context.rs:315:13
  19:     0x559fa4ddc7b1 - tokio::runtime::scheduler::multi_thread::MultiThread::block_on::h16d0d92c0637faa8
                               at /home/fri/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/scheduler/multi_thread/mod.rs:66:9
  20:     0x559fa4de6278 - tokio::runtime::runtime::Runtime::block_on::ha769ab0ab12ced3c
                               at /home/fri/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/runtime.rs:304:45
  21:     0x559fa4ddbf7b - server::main::hdb4f40228dcfe0ac
                               at /robba/tmp/wtransport/wtransport/examples/server.rs:20:5
  22:     0x559fa4ddcd5b - core::ops::function::FnOnce::call_once::h7a44d2145209746b
                               at /builddir/build/BUILD/rustc-1.70.0-src/library/core/src/ops/function.rs:250:5
  23:     0x559fa4de0c3e - std::sys_common::backtrace::__rust_begin_short_backtrace::h4be0ad8be2eb05ee
                               at /builddir/build/BUILD/rustc-1.70.0-src/library/std/src/sys_common/backtrace.rs:134:18
  24:     0x559fa4dda901 - std::rt::lang_start::{{closure}}::h587d1736801f902c
                               at /builddir/build/BUILD/rustc-1.70.0-src/library/std/src/rt.rs:166:18
  25:     0x559fa529a60e - std::rt::lang_start_internal::h8471d15dead791e4
  26:     0x559fa4dda8da - std::rt::lang_start::h3942d2b55594a650
                               at /builddir/build/BUILD/rustc-1.70.0-src/library/std/src/rt.rs:165:17
  27:     0x559fa4ddc02e - main
  28:     0x7f7fae884b4a - __libc_start_call_main
  29:     0x7f7fae884c0b - __libc_start_main@@GLIBC_2.34
  30:     0x559fa4dd54c5 - _start
  31:                0x0 - <unknown>

Could it be that Firefox 114 uses a different version of the protocol?

dnsResolver is not send

Hi,
I had to upgrade to master to get a Certificate struct that derives Clone.
However my future doesn't work anymore

        tokio::spawn(async move {
            let config = ClientConfig::builder()
                .with_bind_address(client_addr)
                .with_no_cert_validation()
                .build();
            let server_url = format!("https://{}", server_addr);
            debug!(
                "Starting client webtransport task with server url: {}",
                &server_url
            );
            let Ok(connection) = wtransport::Endpoint::client(config)
                .unwrap()
                .connect(server_url)
                .await
            else {
                error!("failed to connect to server");
                return;
            };
     })

The error I get is

help: the trait `Send` is not implemented for `(dyn DnsResolver + 'static)`
note: future is not `Send` as this value is used across an await

Which I did not get on 0.1.8
Is that expected? How can i get around this?

Axum integration

What is the best/easiest way to integrate this in a REST API server based on Axum?

Potential DoS on frame payload allocation

Current version (i.e., 0.1.5) is affected by a potential Denial-of-Service attack.

When parsing h3 Frame, the payload is heap-allocated without any particular check: https://github.com/BiagioFesta/wtransport/blob/master/wtransport-proto/src/frame.rs#L213

An attack could forge a "bomb" packet with a fake frame containing a payload_len section coding a big payload.

Server can crash because of memory failure on memory allocation:

2023-10-02T19:45:18.579127Z DEBUG Connection{id=0}:Driver{quic_id=139805145006032}: wtransport::driver::worker: Started
2023-10-02T19:45:18.579185Z DEBUG Connection{id=0}:Driver{quic_id=139805145006032}: wtransport::driver::worker: New incoming uni stream (2)
2023-10-02T19:45:18.579325Z DEBUG Connection{id=0}:Driver{quic_id=139805145006032}:Stream{id=2}: wtransport::driver::worker: Type: Control
memory allocation of 4611686018427387903 bytes failed
Aborted (core dumped)

Chrome does not connect to the wtransport server

Discovered this issue recently.

Most likely caused by this: https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.cc;l=1303;drc=5cb22f99522203e552ff9de07dbc895f26a96dd4;bpv=1;bpt=1

(see https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.h;l=687;drc=5cb22f99522203e552ff9de07dbc895f26a96dd4;bpv=1;bpt=1)

When 0xc671706a setting is activated, it actually enables draft-07 instead of draft-02 which would otherwise be the max version; this in turn causes the version negotiation at the Chrome side to select draft-07, which wtransport doesn't support (until #42).

As can be seen from the link above, at Chrome the 0xc671706a corresponds to SETTINGS_WEBTRANS_MAX_SESSIONS_DRAFT07 - with DRAFT07 in the name, so this is a hint that it will trigger draft07.

With all this, Chrome fails the connection with the following error: WebTransport over HTTP/3 version draft-07 and beyond requires the RFC version of HTTP datagrams. It suggests we need to complete #42, or use draft-02 and avoid touching draft-07. Or there might be a simple missing setting for HTTP/3 datagrams or sth like that - I didn't check that yet.

Expose datagram content as Bytes?

Thank you for your library, very useful for my experiments with WebTransport ๐Ÿ‘

A Datagram internally uses a Bytes instance, but only exposes a Deref<Target=[u8]>.

On one hand, it may be useful for the implementation because it can change from Bytes to something else internally without changing the API. But on the other hand, the caller is forced to copy the content, even if it uses Bytes:

let datagram = conn.receive_datagram().await?;
let bytes = Bytes::copy_from_slice(&datagram);

Since Bytes is very common, and is also exposed in the quinn API, it might make sense to expose the payload as a Bytes instance. Something like this:

diff --git a/wtransport/src/datagram.rs b/wtransport/src/datagram.rs
index f2cd4dc..419bcab 100644
--- a/wtransport/src/datagram.rs
+++ b/wtransport/src/datagram.rs
@@ -57,6 +57,11 @@ impl Datagram {
     pub(crate) fn into_quic_bytes(self) -> Bytes {
         self.quic_dgram
     }
+
+    /// Return the payload
+    pub fn payload(&self) -> Bytes {
+        self.quic_dgram.slice(self.payload_offset..)
+    }
 }
 
 impl Deref for Datagram {

This would allow to avoid a whole copy for each datagram received.

What do you think?

Random segfault when reconnecting

Sometimes (I hate these kinds of bugs, as they're very difficult to reproduce), when I run the server (OS: Fedora 38 x86-64), and try to connect from Firefox (version 115.0.3 as of now, but version 114 behaved the same) from the https://webtransport.day website, the server segfaults with a ton of these messages:

2023-08-01T08:36:00.648896Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.648932Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.648967Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649003Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649038Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649074Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649110Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649146Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649182Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649217Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649253Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649288Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649324Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649367Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649404Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649440Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649484Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649521Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649558Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649593Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649629Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649665Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649700Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649736Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649771Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649807Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649844Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649879Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649915Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649950Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.649986Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650021Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650057Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650093Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650129Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650165Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650201Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650236Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650272Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650308Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650343Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650397Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650433Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650468Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650502Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650537Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650572Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650606Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650642Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650677Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650713Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650747Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650782Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650816Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650851Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650886Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650920Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650955Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.650990Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651025Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651060Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651094Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651129Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651164Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651199Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651233Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651268Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651303Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651337Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651400Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651447Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651483Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651519Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651555Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651590Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651626Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651662Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651697Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651733Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651768Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651804Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651839Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651876Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651912Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651948Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.651983Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652019Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652054Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652090Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652125Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652161Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652197Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652232Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652268Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652304Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652339Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652394Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652430Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652464Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652499Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652534Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026
2023-08-01T08:36:00.652568Z DEBUG quinn_proto::endpoint: sending stateless reset for 7b35de7faa00c48a to [::1]:45026fish: Job 1, 'env RUST_BACKTRACE=full mix runโ€ฆ' terminated by signal SIGABRT (Abort)

Note that there are many more of these messages than what I pasted here.
To somewhat reproduce the problem, start the server, connect form Firefox, then reload the page and connect again.
Sometimes it crashes, and sometimes not.

Create wtransport::Certificate directly from rcgen::Certificate

I am writing a test, where I need to pass in a wtransport::Certificate to the ServerConfig.

I wanted to generate the wtransport::Certificate on the fly by calling (copied from your gencert.rs example)

const COMMON_NAME: &str = "localhost";

let mut dname = DistinguishedName::new();
dname.push(DnType::CommonName, COMMON_NAME);

let keypair = KeyPair::generate(&PKCS_ECDSA_P256_SHA256).unwrap();

let digest = digest(&SHA256, &keypair.public_key_der());

let mut cert_params = CertificateParams::new(vec![COMMON_NAME.to_string()]);
cert_params.distinguished_name = dname;
cert_params.alg = &PKCS_ECDSA_P256_SHA256;
cert_params.key_pair = Some(keypair);
cert_params.not_before = OffsetDateTime::now_utc()
    .checked_sub(Duration::days(2))
    .unwrap();
cert_params.not_after = OffsetDateTime::now_utc()
    .checked_add(Duration::days(2))
    .unwrap();

let certificate = rcgen::Certificate::from_params(cert_params).unwrap();

and then converting the rcgen::Certificate, but I could not find an easy way to convert the rcgen::Certificate into a wtransport::Certificate apart from creating cert.pem and key.pem files.
This is slightly unwieldy for tests as I have to remember to delete those files after running the tests; maybe there could be a way to make this conversion without saving the certificate to local files?

Log/Tracing

Integrate logging into the library (using tracing crate)

async framework agnosticism?

This a great project. I have a question though about the goal in regards to async support. I see that as things are right now there's a dependency on tokio -- which I think rules out use w/ other async runtimes (like smol or glommio, etc.)

It seems most uses are just on tokio::sync, for which there are potential platform agnostic alternatives. (async_channel etc.).

However there is one use of tokio::spawn @

and I don't think there's a way to do the equivalent without a runtime dependency. So I guess there'd need to be some refactoring to make that feasible (perhaps a trait and separate pluggable TokioDriver behind a feature or separate crate?)

Curious what the thoughts of the project maintainers are here. Thanks!

wasm support?

Hi,
I apologize if it's a dumb question.

I was trying to build my code for wasm32-unknown-unknown and I was getting a failure because of the ring library.
(not sure why, because apparently ring is compatible with wasm)

Does this library build for wasm?
Is there a plan to support this in the future?

Settings Validation

Implement settings validation for both side, server and client.

However, draft versions changed IANA settings code. Chrome seems to be support the old draft

draft07 support

This project is still at draft-02, would be nice to move forward to catch up with the browsers.

Expose more configuration options

It would be great to have more configuration options available for both client and server.

I am in particular looking for more options to configure the certificates (i.e. on the server we need to rotate them), the low-level access to rustls configs (for things like SSLKEYLOGFILE), the ability to tweak the underlying socket.
Also, a way to do a custom DNS resolution would be nice (like with https://github.com/MOZGIII/hyper-system-resolver sometimes I want to hint which IP family I prefer/want to force A/AAAA records).

Working with Desktop clients

Looking at this repo, it seems pretty cool. However, I'm curious. Could you use this library to make a desktop client, and have it be able to connect to the same server and communicate with a web-based client?

error: failed to run custom build command for `ls-qpack-sys v0.1.3`

Hi,
I'm running on Windows 10. Tried to run on command prompt

cargo run --example full

It gives an error

C:\Users\Avyan\Downloads\wtransport>cargo run --example full
Compiling ls-qpack-sys v0.1.3
Compiling serde_path_to_error v0.1.14
Compiling sharded-slab v0.1.7
Compiling thread_local v1.1.7
Compiling tracing-log v0.2.0
Compiling matchit v0.7.3
Compiling smallvec v1.11.2
Compiling anyhow v1.0.75
error: failed to run custom build command for ls-qpack-sys v0.1.3

Caused by:
process didn't exit successfully: C:\Users\Avyan\Downloads\wtransport\target\debug\build\ls-qpack-sys-4d26a347265ee188\build-script-build (exit code: 101)
--- stdout
CMAKE_TOOLCHAIN_FILE_x86_64-pc-windows-msvc = None
CMAKE_TOOLCHAIN_FILE_x86_64_pc_windows_msvc = None
HOST_CMAKE_TOOLCHAIN_FILE = None
CMAKE_TOOLCHAIN_FILE = None
CMAKE_GENERATOR_x86_64-pc-windows-msvc = None
CMAKE_GENERATOR_x86_64_pc_windows_msvc = None
HOST_CMAKE_GENERATOR = None
CMAKE_GENERATOR = None
CMAKE_PREFIX_PATH_x86_64-pc-windows-msvc = None
CMAKE_PREFIX_PATH_x86_64_pc_windows_msvc = None
HOST_CMAKE_PREFIX_PATH = None
CMAKE_PREFIX_PATH = None
CMAKE_x86_64-pc-windows-msvc = None
CMAKE_x86_64_pc_windows_msvc = None
HOST_CMAKE = None
CMAKE = None
running: "cmake" "C:\Users\Avyan\.cargo\registry\src\index.crates.io-6f17d22bba15001f\ls-qpack-sys-0.1.3\deps/ls-qpack" "-G" "Visual Studio 17 2022" "-Thost=x64" "-Ax64" "-DLSQPACK_BIN=OFF" "-DCMAKE_INSTALL_PREFIX=C:\Users\Avyan\Downloads\wtransport\target\debug\build\ls-qpack-sys-e6204c3bc1a0151d\out" "-DCMAKE_C_FLAGS= -nologo -MD -Brepro" "-DCMAKE_C_FLAGS_DEBUG= -nologo -MD -Brepro" "-DCMAKE_CXX_FLAGS= -nologo -MD -Brepro" "-DCMAKE_CXX_FLAGS_DEBUG= -nologo -MD -Brepro" "-DCMAKE_ASM_FLAGS= -nologo -MD -Brepro" "-DCMAKE_ASM_FLAGS_DEBUG= -nologo -MD -Brepro" "-DCMAKE_BUILD_TYPE=Debug"

--- stderr
thread 'main' panicked at C:\Users\Avyan.cargo\registry\src\index.crates.io-6f17d22bba15001f\cmake-0.1.50\src\lib.rs:1098:5:

failed to execute command: program not found
is cmake not installed?

build script failed, must exit now
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...

Can you help please ?

A call to connection.closed() never seems to resolve

In my server configuration I poll for connection.closed(), which should resolve when the remote end of the connection is closed.
Now, I tried to connect from https://webtransport.day , and all the connection and data sending/receiving went OK.
After closing the browser tab, I expect the connection.closed() call to resolve, but it doesn't (at least not in the 5 minutes that I waited).

I also tried to configure the server config setting config_builder.max_idle_timeout(Some(Duration::from_secs(30))) and waited for a timeout to happen.
Just to be sure, I also lowered the number to 2, but nothing, the connection.closed() call never seems to resolve.

Please implement the Clone trait for the Certificate

I'd like to be able to do the following:

            let certificate = Certificate::load(&certfile, &keyfile).await?;

            let endpoints = addrs_iter
                .map(|bind_address| {
                    let config = ServerConfig::builder()
                        .with_bind_address(bind_address)
                        .with_certificate(certificate.clone())
                        .keep_alive_interval(Some(Duration::from_secs(3)))
                        .build();

                    Ok((Endpoint::server(config)?, bind_address))
                })
                .collect::<Result<Vec<(Endpoint<Server>, SocketAddr)>, io::Error>>()?;

So that I can load the certificates from disk only once, and be able to create multiple endpoints with the same certificates.

I was thinking that if the Certificate struct implements the Clone trait, then this would be possible, right?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.