Skip to content

Commit

Permalink
Add support for WebRTC (#2579)
Browse files Browse the repository at this point in the history
Closes #1712

Co-authored-by: Pierre Krieger <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 20, 2022
1 parent 07d35b4 commit 405c38c
Show file tree
Hide file tree
Showing 14 changed files with 500 additions and 128 deletions.
27 changes: 18 additions & 9 deletions bin/light-base/src/network_service/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use super::Shared;
use crate::platform::{Platform, PlatformConnection, PlatformSubstreamDirection};

use alloc::{string::ToString as _, sync::Arc, vec};
use alloc::{string::ToString as _, sync::Arc, vec, vec::Vec};
use core::{iter, pin::Pin};
use futures::{channel::mpsc, prelude::*};
use smoldot::{libp2p::read_write::ReadWrite, network::service};
Expand Down Expand Up @@ -372,6 +372,8 @@ async fn multi_stream_connection_task<TPlat: Platform>(
let mut pending_opening_out_substreams = 0;
// Newly-open substream that has just been yielded by the connection.
let mut newly_open_substream = None;
// `true` if the remote has force-closed our connection.
let mut has_reset = false;
// List of all currently open substreams. The index (as a `usize`) corresponds to the id
// of this substream within the `connection_task` state machine.
let mut open_substreams = slab::Slab::<TPlat::Stream>::with_capacity(16);
Expand Down Expand Up @@ -433,12 +435,12 @@ async fn multi_stream_connection_task<TPlat: Platform>(
}

// Perform a read-write on all substreams that are ready.
loop {
let substream_id = match connection_task.ready_substreams().next() {
Some(s) => *s,
None => break,
};

// TODO: what is a ready substream is a bit of a clusterfuck, figure out
for substream_id in connection_task
.ready_substreams()
.copied()
.collect::<Vec<_>>()
{
let substream = &mut open_substreams[substream_id];

let mut read_write = ReadWrite {
Expand Down Expand Up @@ -576,8 +578,15 @@ async fn multi_stream_connection_task<TPlat: Platform>(
debug_assert!(newly_open_substream.is_none());
futures::select! {
_ = message_from_coordinator => {}
substream = TPlat::next_substream(&mut connection).fuse() => {
newly_open_substream = substream;
substream = if has_reset { either::Right(future::pending()) } else { either::Left(TPlat::next_substream(&mut connection)) }.fuse() => {
match substream {
Some(s) => newly_open_substream = Some(s),
None => {
// `None` is returned if the remote has force-closed the connection.
connection_task.reset();
has_reset = true;
}
}
}
_ = poll_after => {}
_ = data_ready.fuse() => {}
Expand Down
1 change: 1 addition & 0 deletions bin/wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### Added

- Add experimental support for WebRTC according to the in-progress specification for libp2p-webrtc. For now this feature must explicitly be enabled by passing `enableExperimentalWebRTC: true` as part of the ̀`ClientConfig`. The multiaddress format for WebRTC is `/ip4/.../udp/.../webrtc/certhash/...` (or `/ip6/...`), where the payload behind `/certhash` is a multibase-encoded multihash-encoded SHA256 of the DTLS certificate used by the remote. ([#2579](https://github.com/paritytech/smoldot/pull/2579))
- Add support for the `chainHead_unstable_finalizedDatabase` JSON-RPC method. This JSON-RPC method aims to be a replacement for the `databaseContent` method of the `Chain` and is expected to remain a permanently unstable smoldot-specific function. ([#2749](https://github.com/paritytech/smoldot/pull/2749))

## 0.6.33 - 2022-09-13
Expand Down
78 changes: 38 additions & 40 deletions bin/wasm-node/javascript/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 115 additions & 0 deletions bin/wasm-node/javascript/src/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Smoldot
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

let rfc4648Alphabet: Map<string, number> = new Map();
const rfc4648AlphabetAsStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (let i = 0; i < rfc4648AlphabetAsStr.length; ++i) {
rfc4648Alphabet.set(rfc4648AlphabetAsStr[i]!, i)
}

let urlSafeAlphabet: Map<string, number> = new Map();
const urlSafeAlphabetAsStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
for (let i = 0; i < urlSafeAlphabetAsStr.length; ++i) {
urlSafeAlphabet.set(urlSafeAlphabetAsStr[i]!, i)
}

/**
* Decodes a multibase-encoded string.
*
* Throws an exception if the encoding isn't base64 or one of its variants.
*/
export function multibaseBase64Decode(input: string): Uint8Array {
if (input.length === 0)
throw new Error("Invalid multibase");

switch (input[0]) {
case 'm':
case 'M':
return classicDecode(input.slice(1))
case 'u':
case 'U':
return urlSafeDecode(input.slice(1))
default:
throw new Error('Unknown multibase prefix: ' + input[0]);
}
}

/**
* Decodes a base64-encoded string into bytes using the original alphabet from RFC4648.
*
* See <https://datatracker.ietf.org/doc/html/rfc4648#section-4>.
*/
export function classicDecode(input: string): Uint8Array {
return base64Decode(input, rfc4648Alphabet);
}

/**
* Decodes a base64-encoded string into bytes using the URL-safe alphabet.
*
* See <https://datatracker.ietf.org/doc/html/rfc4648#section-5>.
*/
export function urlSafeDecode(input: string): Uint8Array {
return base64Decode(input, urlSafeAlphabet);
}

/**
* Decodes a base64-encoded string into bytes using the given alphabet.
*/
export function base64Decode(input: string, alphabet: Map<string, number>): Uint8Array {
// Remove the padding bytes at the end of the string. We don't check whether the padding is
// accurate.
while (input.length !== 0 && input[input.length - 1] === '=')
input = input.slice(0, -1)

// Contains the output data.
const out = new Uint8Array(Math.floor(input.length * 6 / 8));
// Position within `out` of the next byte to write.
let outPos = 0;

// The bits decoded from the input are added to the right of this value.
let currentByte = 0;
// The left-most `validBitsInCurrentByte` bits of `currentByte` must be written out.
let validBitsInCurrentByte = 0;

for (let i = 0; i < input.length; ++i) {
const inputChr = input[i]!;

const bitsToAppend = alphabet.get(inputChr);
if (bitsToAppend === undefined)
throw new Error('Invalid base64 character: ' + inputChr);
console.assert(bitsToAppend < (1 << 6));

currentByte = (currentByte << 6) | bitsToAppend;
validBitsInCurrentByte += 6;

if (validBitsInCurrentByte >= 8) {
let outByte = currentByte >> (validBitsInCurrentByte - 8);
out[outPos] = outByte;
outPos += 1;
validBitsInCurrentByte -= 8;
}
console.assert(validBitsInCurrentByte < 8);
currentByte &= 0xff;
}

if ((currentByte & ((1 << validBitsInCurrentByte) - 1)) !== 0)
throw new Error("Unexpected EOF");
if (validBitsInCurrentByte >= 6)
throw new Error("Unexpected EOF");

return out;
}
10 changes: 10 additions & 0 deletions bin/wasm-node/javascript/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,16 @@ export interface ClientOptions {
* connections.
*/
forbidWss?: boolean;

/**
* Enable experimental support for WebRTC connections.
*
* Support for WebRTC connections is currently in progress and might have significant issues.
*
* This flag currently defaults to `false`. In a later version, it will be removed and WebRTC
* connections will be enabled by default.
*/
enableExperimentalWebRTC?: boolean;
}

/**
Expand Down
Loading

0 comments on commit 405c38c

Please sign in to comment.