-
Notifications
You must be signed in to change notification settings - Fork 110
[WIP] Rust TokenSource implementation #749
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
base: main
Are you sure you want to change the base?
Conversation
…onous traits These allow for ? to be used within their fn bodies
Probably this won't be what we want longer term but I wanted to start somewhere so folks can try it out.
/// There is also a predefined list of labels that can be used to reference common metrics. | ||
/// They have reserved indices from 0 to (METRIC_LABEL_PREDEFINED_MAX_VALUE - 1). | ||
/// Indexes pointing at str_data should start from METRIC_LABEL_PREDEFINED_MAX_VALUE, | ||
/// Indexes pointing at str_data should start from METRIC_LABEL_PREDEFINED_MAX_VALUE, | ||
/// such that str_data\[0\] == index of METRIC_LABEL_PREDEFINED_MAX_VALUE. | ||
#[prost(string, repeated, tag="3")] | ||
pub str_data: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, | ||
#[prost(message, repeated, tag="4")] | ||
pub time_series: ::prost::alloc::vec::Vec<TimeSeriesMetric>, | ||
#[prost(message, repeated, tag="5")] | ||
pub events: ::prost::alloc::vec::Vec<EventMetric>, | ||
} | ||
#[allow(clippy::derive_partial_eq_without_eq)] | ||
#[derive(Clone, PartialEq, ::prost::Message)] | ||
pub struct TimeSeriesMetric { | ||
/// Metric name e.g "speech_probablity". The string value is not directly stored in the message, but referenced by index | ||
/// in the `str_data` field of `MetricsBatch` | ||
#[prost(uint32, tag="1")] | ||
pub label: u32, | ||
/// index into `str_data` | ||
#[prost(uint32, tag="2")] | ||
pub participant_identity: u32, | ||
/// index into `str_data` | ||
#[prost(uint32, tag="3")] | ||
pub track_sid: u32, | ||
#[prost(message, repeated, tag="4")] | ||
pub samples: ::prost::alloc::vec::Vec<MetricSample>, | ||
/// index into 'str_data' | ||
#[prost(uint32, tag="5")] | ||
pub rid: u32, | ||
} | ||
#[allow(clippy::derive_partial_eq_without_eq)] | ||
#[derive(Clone, PartialEq, ::prost::Message)] | ||
pub struct MetricSample { | ||
/// time of metric based on a monotonic clock (in milliseconds) | ||
#[prost(int64, tag="1")] | ||
pub timestamp_ms: i64, | ||
#[prost(message, optional, tag="2")] | ||
pub normalized_timestamp: ::core::option::Option<::pbjson_types::Timestamp>, | ||
#[prost(float, tag="3")] | ||
pub value: f32, | ||
} | ||
#[allow(clippy::derive_partial_eq_without_eq)] | ||
#[derive(Clone, PartialEq, ::prost::Message)] | ||
pub struct EventMetric { | ||
#[prost(uint32, tag="1")] | ||
pub label: u32, | ||
/// index into `str_data` | ||
#[prost(uint32, tag="2")] | ||
pub participant_identity: u32, | ||
/// index into `str_data` | ||
#[prost(uint32, tag="3")] | ||
pub track_sid: u32, | ||
/// start time of event based on a monotonic clock (in milliseconds) | ||
#[prost(int64, tag="4")] | ||
pub start_timestamp_ms: i64, | ||
/// end time of event based on a monotonic clock (in milliseconds), if needed | ||
#[prost(int64, optional, tag="5")] | ||
pub end_timestamp_ms: ::core::option::Option<i64>, | ||
#[prost(message, optional, tag="6")] | ||
pub normalized_start_timestamp: ::core::option::Option<::pbjson_types::Timestamp>, | ||
#[prost(message, optional, tag="7")] | ||
pub normalized_end_timestamp: ::core::option::Option<::pbjson_types::Timestamp>, | ||
#[prost(string, tag="8")] | ||
pub metadata: ::prost::alloc::string::String, | ||
/// index into 'str_data' | ||
#[prost(uint32, tag="9")] | ||
pub rid: u32, | ||
} | ||
#[allow(clippy::derive_partial_eq_without_eq)] | ||
#[derive(Clone, PartialEq, ::prost::Message)] | ||
pub struct MetricsRecordingHeader { | ||
#[prost(string, tag="1")] | ||
pub room_id: ::prost::alloc::string::String, | ||
#[prost(bool, optional, tag="2")] | ||
pub enable_user_data_training: ::core::option::Option<bool>, | ||
} | ||
// | ||
// Protocol used to record metrics for a specific session. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to update the livekit-protocol
package to the latest in order to get access to TokenSourceRequest
/ TokenSourceResponse
from here - that's where the majority of the diff changes are coming from:

Is this something worth doing as a separate pull request merged in as a prerequisite or is it fine to include this broader change in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is we are using an old version of prost, and when you install the generator locally, you get the latest by default. Options:
- Install the correct generator:
cargo install [email protected] [email protected]
- Take this PR out of draft, and the CI will generate for you using the correct version
I'm going to create a ticket to remind us we need to upgrade to prost 14.x.x.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed the instructions in here so I think I did generate these with the correct version, FYI. That being said I also added a note for myself to add some better documentation (maybe add it to the README.md
?) on how to generate these, it took me longer than it should have for me to self-discover how.
Sip, | ||
Agent, | ||
Connector, | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Along with the livekit-protocol
changes, I had to add a new corresponding Connector
value to this enum to get it to compile again. I don't know what this pertains to / why this was added, so I wanted to call it out just to make sure somebody else more familiar with the state of this repository can verify I did the right thing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, yea, I didn't do something right here and this is what is causing the ci build to break... 😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see the issue. Since you added the Connector variant in the public ParticipantKind enum, you are getting a build error because this isn't being exposed over FFI. Steps to fix:
- Add the variant in the FFI protocol in livekit-ffi/protocol/participant.proto
- Add a match arm in livekit-ffi/src/conversion/participant.rs (this is the source of the build error)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am also curious what a connector participant is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see - I didn't realize there were separate FFI protobuf implementations which is what was tripping me up. Just made that change and was able to do a build of livekit-ffi
locally, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like CI is now passing everywhere except for android for an unknown reason. Any thoughts on why? It doesn't at least at surface level seem to be related to my change but I'm not 100% sure.
Failing android build logs:
error: failed to run custom build command for `openssl-sys v0.9.109`
Caused by:
process didn't exit successfully: `CARGO=/home/runner/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/cargo CARGO_CFG_FEATURE='' CARGO_CFG_PANIC=unwind CARGO_CFG_TARGET_ABI='' CARGO_CFG_TARGET_ARCH=aarch64 CARGO_CFG_TARGET_ENDIAN=little CARGO_CFG_TARGET_ENV='' CARGO_CFG_TARGET_FAMILY=unix CARGO_CFG_TARGET_FEATURE=neon CARGO_CFG_TARGET_HAS_ATOMIC=128,16,32,64,8,ptr CARGO_CFG_TARGET_OS=android CARGO_CFG_TARGET_POINTER_WIDTH=64 CARGO_CFG_TARGET_VENDOR=unknown CARGO_CFG_UNIX='' CARGO_ENCODED_RUSTFLAGS='' CARGO_MANIFEST_DIR=/home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/openssl-sys-0.9.109 CARGO_MANIFEST_LINKS=openssl CARGO_MANIFEST_PATH=/home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/openssl-sys-0.9.109/Cargo.toml CARGO_PKG_AUTHORS='Alex Crichton <[email protected]>:Steven Fackler <[email protected]>' CARGO_PKG_DESCRIPTION='FFI bindings to OpenSSL' CARGO_PKG_HOMEPAGE='' CARGO_PKG_LICENSE=MIT CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=openssl-sys CARGO_PKG_README=README.md CARGO_PKG_REPOSITORY='https://github.com/sfackler/rust-openssl' CARGO_PKG_RUST_VERSION=1.63.0 CARGO_PKG_VERSION=0.9.109 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=9 CARGO_PKG_VERSION_PATCH=109 CARGO_PKG_VERSION_PRE='' DEBUG=false HOST=x86_64-unknown-linux-gnu LD_LIBRARY_PATH='/home/runner/work/rust-sdks/rust-sdks/target/release/deps:/home/runner/work/rust-sdks/rust-sdks/target/release:/home/runner/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib:/home/runner/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib' NUM_JOBS=4 OPT_LEVEL=3 OUT_DIR=/home/runner/work/rust-sdks/rust-sdks/target/aarch64-linux-android/release/build/openssl-sys-855bcbdb9481fece/out PROFILE=release RUSTC=/home/runner/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc RUSTC_LINKER=/home/runner/.cargo/bin/cargo-ndk RUSTDOC=/home/runner/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustdoc TARGET=aarch64-linux-android /home/runner/work/rust-sdks/rust-sdks/target/release/build/openssl-sys-9005c0badb4c225b/build-script-main` (exit status: 101)
--- stdout
cargo:rustc-check-cfg=cfg(osslconf, values("OPENSSL_NO_OCB", "OPENSSL_NO_SM4", "OPENSSL_NO_SEED", "OPENSSL_NO_CHACHA", "OPENSSL_NO_CAST", "OPENSSL_NO_IDEA", "OPENSSL_NO_CAMELLIA", "OPENSSL_NO_RC4", "OPENSSL_NO_BF", "OPENSSL_NO_PSK", "OPENSSL_NO_DEPRECATED_3_0", "OPENSSL_NO_SCRYPT", "OPENSSL_NO_SM3", "OPENSSL_NO_RMD160", "OPENSSL_NO_EC2M", "OPENSSL_NO_OCSP", "OPENSSL_NO_CMS", "OPENSSL_NO_COMP", "OPENSSL_NO_SOCK", "OPENSSL_NO_STDIO", "OPENSSL_NO_EC", "OPENSSL_NO_SSL3_METHOD", "OPENSSL_NO_KRB5", "OPENSSL_NO_TLSEXT", "OPENSSL_NO_SRP", "OPENSSL_NO_RFC3779", "OPENSSL_NO_SHA", "OPENSSL_NO_NEXTPROTONEG", "OPENSSL_NO_ENGINE", "OPENSSL_NO_BUF_FREELISTS", "OPENSSL_NO_RC2"))
cargo:rustc-check-cfg=cfg(openssl)
cargo:rustc-check-cfg=cfg(libressl)
cargo:rustc-check-cfg=cfg(boringssl)
cargo:rustc-check-cfg=cfg(awslc)
cargo:rustc-check-cfg=cfg(libressl250)
cargo:rustc-check-cfg=cfg(libressl251)
cargo:rustc-check-cfg=cfg(libressl252)
cargo:rustc-check-cfg=cfg(libressl261)
cargo:rustc-check-cfg=cfg(libressl270)
cargo:rustc-check-cfg=cfg(libressl271)
cargo:rustc-check-cfg=cfg(libressl273)
cargo:rustc-check-cfg=cfg(libressl280)
cargo:rustc-check-cfg=cfg(libressl281)
cargo:rustc-check-cfg=cfg(libressl291)
cargo:rustc-check-cfg=cfg(libressl310)
cargo:rustc-check-cfg=cfg(libressl321)
cargo:rustc-check-cfg=cfg(libressl332)
cargo:rustc-check-cfg=cfg(libressl340)
cargo:rustc-check-cfg=cfg(libressl350)
cargo:rustc-check-cfg=cfg(libressl360)
cargo:rustc-check-cfg=cfg(libressl361)
cargo:rustc-check-cfg=cfg(libressl370)
cargo:rustc-check-cfg=cfg(libressl380)
cargo:rustc-check-cfg=cfg(libressl381)
cargo:rustc-check-cfg=cfg(libressl382)
cargo:rustc-check-cfg=cfg(libressl390)
cargo:rustc-check-cfg=cfg(libressl400)
cargo:rustc-check-cfg=cfg(libressl410)
cargo:rustc-check-cfg=cfg(ossl101)
cargo:rustc-check-cfg=cfg(ossl102)
cargo:rustc-check-cfg=cfg(ossl102f)
cargo:rustc-check-cfg=cfg(ossl102h)
cargo:rustc-check-cfg=cfg(ossl110)
cargo:rustc-check-cfg=cfg(ossl110f)
cargo:rustc-check-cfg=cfg(ossl110g)
cargo:rustc-check-cfg=cfg(ossl110h)
cargo:rustc-check-cfg=cfg(ossl111)
cargo:rustc-check-cfg=cfg(ossl111b)
cargo:rustc-check-cfg=cfg(ossl111c)
cargo:rustc-check-cfg=cfg(ossl111d)
cargo:rustc-check-cfg=cfg(ossl300)
cargo:rustc-check-cfg=cfg(ossl310)
cargo:rustc-check-cfg=cfg(ossl320)
cargo:rustc-check-cfg=cfg(ossl330)
cargo:rustc-check-cfg=cfg(ossl340)
cargo:rerun-if-env-changed=AARCH64_LINUX_ANDROID_OPENSSL_LIB_DIR
AARCH64_LINUX_ANDROID_OPENSSL_LIB_DIR unset
cargo:rerun-if-env-changed=OPENSSL_LIB_DIR
OPENSSL_LIB_DIR unset
cargo:rerun-if-env-changed=AARCH64_LINUX_ANDROID_OPENSSL_INCLUDE_DIR
AARCH64_LINUX_ANDROID_OPENSSL_INCLUDE_DIR unset
cargo:rerun-if-env-changed=OPENSSL_INCLUDE_DIR
OPENSSL_INCLUDE_DIR unset
cargo:rerun-if-env-changed=AARCH64_LINUX_ANDROID_OPENSSL_DIR
AARCH64_LINUX_ANDROID_OPENSSL_DIR unset
cargo:rerun-if-env-changed=OPENSSL_DIR
OPENSSL_DIR unset
cargo:rerun-if-env-changed=OPENSSL_NO_PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS_aarch64-linux-android
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS_aarch64_linux_android
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG_ALLOW_CROSS
cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS
cargo:rerun-if-env-changed=PKG_CONFIG_aarch64-linux-android
cargo:rerun-if-env-changed=PKG_CONFIG_aarch64_linux_android
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_aarch64-linux-android
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_aarch64_linux_android
cargo:rerun-if-env-changed=TARGET_PKG_CONFIG_SYSROOT_DIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
Could not find openssl via pkg-config:
pkg-config has not been configured to support cross-compilation.
Install a sysroot for the target platform and configure it via
PKG_CONFIG_SYSROOT_DIR and PKG_CONFIG_PATH, or install a
cross-compiling wrapper for pkg-config and set it via
PKG_CONFIG environment variable.
cargo:warning=Could not find directory of OpenSSL installation, and this `-sys` crate cannot proceed without this knowledge. If OpenSSL is installed and this crate had trouble finding it, you can set the `OPENSSL_DIR` environment variable for the compilation process. See stderr section below for further information.
--- stderr
Could not find directory of OpenSSL installation, and this `-sys` crate cannot
proceed without this knowledge. If OpenSSL is installed and this crate had
trouble finding it, you can set the `OPENSSL_DIR` environment variable for the
compilation process.
Make sure you also have the development packages of openssl installed.
For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.
If you're in a situation where you think the directory *should* be found
automatically, please open a bug at https://github.com/sfackler/rust-openssl
and include information about your system as well as this message.
$HOST = x86_64-unknown-linux-gnu
$TARGET = aarch64-linux-android
openssl-sys = 0.9.109
|
||
impl<F: Fn() -> TokenSourceResponse> TokenLiteralGenerator for F { | ||
// FIXME: allow this to be an async fn! | ||
fn apply(&self) -> TokenSourceResponse { | ||
self() | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: this needs to be changed to accept an async function, so you can do something like:
let _ = TokenSourceLiteral::new(async || {
Ok(TokenSourceResponse { /* ... */})
});
|
||
// FIXME: apply options in the below code! | ||
let participant_token = access_token::AccessToken::with_api_key(&api_key, &api_secret) | ||
.with_identity("rust-bot") | ||
.with_name("Rust Bot") | ||
.with_grants(access_token::VideoGrants { | ||
room_join: true, | ||
room: "my-room".to_string(), | ||
..Default::default() | ||
}) | ||
.to_jwt()?; | ||
|
||
Ok(TokenSourceResponse { server_url, participant_token }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: finish this up by wiring all the options
values through to the corresponding with_
prefixed builder functions!
pub struct TokenSourceCustom< | ||
CustomFn: Fn( | ||
&TokenSourceFetchOptions, | ||
) -> Pin<Box<dyn Future<Output = Result<TokenSourceResponse, Box<dyn Error>>>>>, | ||
>(CustomFn); | ||
|
||
impl< | ||
CustomFn: Fn( | ||
&TokenSourceFetchOptions, | ||
) -> Pin<Box<dyn Future<Output = Result<TokenSourceResponse, Box<dyn Error>>>>>, | ||
> TokenSourceCustom<CustomFn> | ||
{ | ||
pub fn new(custom_fn: CustomFn) -> Self { | ||
Self(custom_fn) | ||
} | ||
} | ||
|
||
impl< | ||
CustomFn: Fn( | ||
&TokenSourceFetchOptions, | ||
) -> Pin<Box<dyn Future<Output = Result<TokenSourceResponse, Box<dyn Error>>>>>, | ||
> TokenSourceConfigurable for TokenSourceCustom<CustomFn> | ||
{ | ||
async fn fetch( | ||
&self, | ||
options: &TokenSourceFetchOptions, | ||
) -> Result<TokenSourceResponse, Box<dyn Error>> { | ||
(self.0)(options).await | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This TokenSourceCustom
is a bit of a mess and still isn't really doing what I want. Ideally, I'd like to be able to pass an async |options| {}
as CustomFn
but I can't quite get that to work.
The closest I've been able to get is:
let custom = TokenSourceCustom::new(|_options| {
Box::pin(future::ready(Ok(TokenSourceResponse {
server_url: "...".into(),
participant_token: "... _options should be encoded in here ...".into(),
})))
});
Maybe somebody else has some ideas?
/// A helper trait to more easily implement a TokenSourceFixed which not async. | ||
pub trait TokenSourceFixedSynchronous { | ||
// FIXME: what should the error type of the result be? | ||
fn fetch_synchronous(&self) -> Result<TokenSourceResponse, Box<dyn Error>>; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wanted to call out: I added -Synchronous
versions of the token source traits to try to cut down on the boilerplate in non async downstream implementors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: What do you think about abbreviating Synchronous to Sync?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally had them ending in Sync
, but my concern was that it would imply some sort of relationship with the Sync
trait. It sounds like you don't think that's a concern though?
…it-protocol upgrade
livekit/src/room/token_source/mod.rs
Outdated
|
||
// FIXME: What are the best practices around implementing Into on a reference to avoid the | ||
// clone? | ||
let request_body: TokenSourceRequest = options.clone().into(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought:
- You currently implement
impl Into<TokenSourceRequest> for TokenSourceFetchOptions
- Try implementing
impl Into<TokenSourceRequest> for &TokenSourceFetchOptions
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense! I think what I was hoping for was some way to transparently handle both impl Into<TokenSourceRequest> for TokenSourceFetchOptions
and impl Into<TokenSourceRequest> for &TokenSourceFetchOptions
through some sort of implicit dereferencing or something like that, but it might just not be possible.
That being said, now that I'm thinking about it more - because TokenSourceRequest
contains String
s, if I'm converting a & TokenSourceFetchOptions
into a TokenSourceRequest
, there's going to have to be some sort of cloning involved at some level unless I did something more crazy with a Cow
/ etc in TokenSourceRequest
.
Because of this, I'm not sure where that clone
should happen, but I tend to think forcing the user to do it makes it clear that it has to happen verses it happening implicitly.
Talking with @ladvoc, it seemed like it would be a good exercise to port the
TokenSource
to rust as a first pass to see what new additional complexity is exposed in a less client focused context. Plus longer term, this could be useful as part of the broader rust client sdk core project.For a working example, see
examples/token_source
. You'll want to run it with some environment variables populated - something maybe like this would work:LIVEKIT_URL=1 LIVEKIT_API_KEY=2 LIVEKIT_API_SECRET=3 SERVER_URL=4 API_KEY=5 API_SECRET=6 RUST_LOG=info cargo run
Warning
General caveat: I've yet to write a substantial amount of rust professionally, though personally I've written a fair bit. Because of this it's fairly likely I'm doing some things badly / in non idiomatic ways. Feel free to point any of that kind of stuff out!
High level summary
The
literal
,endpoint
,sandboxTokenServer
, andcustom
implementations all work very similar to their web, swift, and android counterparts.The big new concept this change introduces is the
TokenSourceMinter
/TokenSourceMinterCustom
. Roughly what they look like:Large remaining things to work through
TokenSourceConfigurable
or maybe aTokenSourceCached
composition layer like swift/android or something like thatTokenSourceCustom
TokenSourceLiteral