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

feat: ✨ Add mDNS discovery support #1978

Merged
merged 1 commit into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
408 changes: 243 additions & 165 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion alvr/client_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ alvr_sockets.workspace = true
app_dirs2 = "2"
bincode = "1"
glyph_brush_layout = "0.2"
jni = "0.21"
mdns-sd = "0.10"
rand = "0.8"
serde = "1"
serde_json = "1"
jni = "0.21"

[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.13"
Expand Down
19 changes: 18 additions & 1 deletion alvr/client_core/src/c_api.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
opengl::{self, RenderViewInput},
ClientCoreEvent,
storage, ClientCoreEvent,
};
use alvr_common::{
debug, error,
Expand Down Expand Up @@ -142,6 +142,23 @@ fn string_to_c_str(buffer: *mut c_char, value: &str) -> u64 {
cstring.as_bytes_with_nul().len() as u64
}

#[no_mangle]
pub extern "C" fn alvr_mdns_service(service_buffer: *mut c_char) -> u64 {
string_to_c_str(service_buffer, alvr_sockets::MDNS_SERVICE_TYPE)
}

// To make sure the value is correct, call after alvr_initialize()
#[no_mangle]
pub extern "C" fn alvr_hostname(hostname_buffer: *mut c_char) -> u64 {
string_to_c_str(hostname_buffer, &storage::Config::load().hostname)
}

// To make sure the value is correct, call after alvr_initialize()
#[no_mangle]
pub extern "C" fn alvr_protocol_id(protocol_buffer: *mut c_char) -> u64 {
string_to_c_str(protocol_buffer, &storage::Config::load().protocol_id)
}

/// On non-Android platforms, java_vm and constext should be null.
/// NB: context must be thread safe.
#[allow(unused_variables)]
Expand Down
2 changes: 1 addition & 1 deletion alvr/client_core/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ fn connection_pipeline(

proto_control_socket
.send(&ClientConnectionResult::ConnectionAccepted {
client_protocol_id: alvr_common::protocol_id(),
client_protocol_id: alvr_common::protocol_id_u64(),
display_name: platform::device_model(),
server_ip,
streaming_capabilities: Some(VideoStreamingCapabilities {
Expand Down
4 changes: 2 additions & 2 deletions alvr/client_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ pub fn initialize(
#[cfg(target_os = "android")]
platform::try_get_permission(platform::MICROPHONE_PERMISSION);
#[cfg(target_os = "android")]
platform::acquire_wifi_lock();
platform::set_wifi_lock(true);

EXTERNAL_DECODER.set(external_decoder);
*LIFECYCLE_STATE.write() = LifecycleState::Idle;
Expand All @@ -123,7 +123,7 @@ pub fn destroy() {
}

#[cfg(target_os = "android")]
platform::release_wifi_lock();
platform::set_wifi_lock(false);
}

pub fn resume() {
Expand Down
98 changes: 57 additions & 41 deletions alvr/client_core/src/platform/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod decoder;

pub use decoder::*;

use alvr_common::OptLazy;
use alvr_common::{warn, OptLazy};
use jni::{
objects::{GlobalRef, JObject},
sys::jobject,
Expand All @@ -12,8 +12,6 @@ use std::net::{IpAddr, Ipv4Addr};

pub const MICROPHONE_PERMISSION: &str = "android.permission.RECORD_AUDIO";

static WIFI_LOCK: OptLazy<GlobalRef> = alvr_common::lazy_mut_none();

pub fn vm() -> JavaVM {
unsafe { JavaVM::from_raw(ndk_context::android_context().vm().cast()).unwrap() }
}
Expand Down Expand Up @@ -137,50 +135,68 @@ pub fn local_ip() -> IpAddr {
}

// This is needed to avoid wifi scans that disrupt streaming.
pub fn acquire_wifi_lock() {
let mut maybe_wifi_lock = WIFI_LOCK.lock();

if maybe_wifi_lock.is_none() {
let vm = vm();
let mut env = vm.attach_current_thread().unwrap();

let wifi_mode = if get_api_level() >= 29 {
// Recommended for virtual reality since it disables WIFI scans
4 // WIFI_MODE_FULL_LOW_LATENCY
} else {
3 // WIFI_MODE_FULL_HIGH_PERF
};

let wifi_manager = get_system_service(&mut env, "wifi");
let wifi_lock_jstring = env.new_string("alvr_wifi_lock").unwrap();
let wifi_lock = env
.call_method(
wifi_manager,
"createWifiLock",
"(ILjava/lang/String;)Landroid/net/wifi/WifiManager$WifiLock;",
&[wifi_mode.into(), (&wifi_lock_jstring).into()],
)
.unwrap()
.l()
.unwrap();
env.call_method(&wifi_lock, "acquire", "()V", &[]).unwrap();
// Code inspired from https://github.com/Meumeu/WiVRn/blob/master/client/application.cpp
pub fn set_wifi_lock(enabled: bool) {
let vm = vm();
let mut env = vm.attach_current_thread().unwrap();

*maybe_wifi_lock = Some(env.new_global_ref(wifi_lock).unwrap());
}
}
let wifi_manager = get_system_service(&mut env, "wifi");
let wifi_lock_jstring = env.new_string("alvr_wifi_lock").unwrap();

pub fn release_wifi_lock() {
if let Some(wifi_lock) = WIFI_LOCK.lock().take() {
let vm = vm();
let mut env = vm.attach_current_thread().unwrap();
fn set_lock<'a>(env: &mut JNIEnv<'a>, lock: &JObject, enabled: bool) {
env.call_method(lock, "setReferenceCounted", "(Z)V", &[false.into()])
.unwrap();
env.call_method(
&lock,
if enabled { "acquire" } else { "release" },
"()V",
&[],
)
.unwrap();

env.call_method(wifi_lock.as_obj(), "release", "()V", &[])
let lock_is_aquired = env
.call_method(lock, "isHeld", "()Z", &[])
.unwrap()
.z()
.unwrap();
// TODO: all JVM.call_method sometimes result in JavaExceptions, unwrap will only report Error as 'JavaException', ideally before unwrapping
// need to call JVM.describe_error() which will actually check if there is an exception and print error to stderr/logcat. Then unwrap.

// wifi_lock is dropped here
if lock_is_aquired != enabled {
warn!("Failed to set wifi lock: expected {enabled}, got {lock_is_aquired}");
}
}

let wifi_lock = env
.call_method(
&wifi_manager,
"createWifiLock",
"(ILjava/lang/String;)Landroid/net/wifi/WifiManager$WifiLock;",
&[
if get_api_level() >= 29 {
// Recommended for virtual reality since it disables WIFI scans
4 // WIFI_MODE_FULL_LOW_LATENCY
} else {
3 // WIFI_MODE_FULL_HIGH_PERF
}
.into(),
(&wifi_lock_jstring).into(),
],
)
.unwrap()
.l()
.unwrap();
set_lock(&mut env, &wifi_lock, enabled);

let multicast_lock = env
.call_method(
wifi_manager,
"createMulticastLock",
"(Ljava/lang/String;)Landroid/net/wifi/WifiManager$MulticastLock;",
&[(&wifi_lock_jstring).into()],
)
.unwrap()
.l()
.unwrap();
set_lock(&mut env, &wifi_lock, enabled);
}

pub fn get_battery_status() -> (f32, bool) {
Expand Down
2 changes: 1 addition & 1 deletion alvr/client_core/src/sockets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ impl AnnouncerSocket {

let mut packet = [0; 56];
packet[0..ALVR_NAME.len()].copy_from_slice(ALVR_NAME.as_bytes());
packet[16..24].copy_from_slice(&alvr_common::protocol_id().to_le_bytes());
packet[16..24].copy_from_slice(&alvr_common::protocol_id_u64().to_le_bytes());
packet[24..24 + hostname.len()].copy_from_slice(hostname.as_bytes());

Ok(Self { socket, packet })
Expand Down
6 changes: 3 additions & 3 deletions alvr/client_core/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ fn config_path() -> PathBuf {

#[derive(Serialize, Deserialize)]
pub struct Config {
pub protocol_id: u64,
pub hostname: String,
pub protocol_id: String,
}

impl Default for Config {
fn default() -> Self {
let mut rng = rand::thread_rng();

Self {
protocol_id: alvr_common::protocol_id(),
hostname: format!(
"{}{}{}{}.client.alvr",
"{}{}{}{}.client",
rng.gen_range(0..10),
rng.gen_range(0..10),
rng.gen_range(0..10),
rng.gen_range(0..10),
),
protocol_id: alvr_common::protocol_id(),
}
}
}
Expand Down
12 changes: 7 additions & 5 deletions alvr/common/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ pub fn is_stable() -> bool {
// Semver compatible versions will produce the same protocol ID. Protocol IDs are not ordered
// As a convention, encode/decode the protocol ID bytes as little endian.
// Only makor and
pub fn protocol_id() -> u64 {
let protocol_string = if ALVR_VERSION.pre.is_empty() {
pub fn protocol_id() -> String {
if ALVR_VERSION.pre.is_empty() {
ALVR_VERSION.major.to_string()
} else {
format!("{}-{}", ALVR_VERSION.major, ALVR_VERSION.pre)
};
}
}

hash_string(&protocol_string)
pub fn protocol_id_u64() -> u64 {
hash_string(&protocol_id())
}

// deprecated
Expand All @@ -44,5 +46,5 @@ pub fn is_version_compatible(other_version: &Version) -> bool {
format!("{}-{}", other_version.major, other_version.pre)
};

protocol_id() == hash_string(&protocol_string)
protocol_id_u64() == hash_string(&protocol_string)
}
2 changes: 2 additions & 0 deletions alvr/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ bincode = "1"
bytes = "1"
chrono = "0.4"
fern = "0.6"
flume = "0.11"
futures = "0.3"
headers = "0.3"
hyper = { version = "0.14", features = [
Expand All @@ -37,6 +38,7 @@ hyper = { version = "0.14", features = [
"runtime",
"tcp",
] }
mdns-sd = "0.10"
profiling = { version = "1", optional = true }
reqwest = "0.11" # not used but webserver does not work without it. todo: investigate
rosc = "0.10"
Expand Down
4 changes: 2 additions & 2 deletions alvr/server/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,10 @@ fn connection_pipeline(
ClientListAction::SetDisplayName(display_name),
);

if client_protocol_id != alvr_common::protocol_id() {
if client_protocol_id != alvr_common::protocol_id_u64() {
warn!(
"Trusted client is incompatible! Expected protocol ID: {}, found: {}",
alvr_common::protocol_id(),
alvr_common::protocol_id_u64(),
client_protocol_id,
);

Expand Down
55 changes: 49 additions & 6 deletions alvr/server/src/sockets.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use alvr_common::{anyhow::Result, warn, ConnectionError, HandleTryAgain, ALVR_NAME};
use alvr_common::{
anyhow::{bail, Result},
warn, ConnectionError, HandleTryAgain, ToAny, ALVR_NAME,
};
use alvr_sockets::{CONTROL_PORT, HANDSHAKE_PACKET_SIZE_BYTES, LOCAL_IP};
use flume::TryRecvError;
use mdns_sd::{Receiver, ServiceDaemon, ServiceEvent};
use std::{
collections::HashMap,
net::{IpAddr, UdpSocket},
};

pub struct WelcomeSocket {
socket: UdpSocket,
buffer: [u8; HANDSHAKE_PACKET_SIZE_BYTES],
broadcast_receiver: UdpSocket,
mdns_receiver: Receiver<ServiceEvent>,
}

impl WelcomeSocket {
Expand All @@ -16,9 +22,12 @@ impl WelcomeSocket {
// socket.set_read_timeout(Some(read_timeout))?;
socket.set_nonblocking(true)?;

let mdns_receiver = ServiceDaemon::new()?.browse(alvr_sockets::MDNS_SERVICE_TYPE)?;

Ok(Self {
socket,
buffer: [0; HANDSHAKE_PACKET_SIZE_BYTES],
broadcast_receiver: socket,
mdns_receiver,
})
}

Expand All @@ -27,7 +36,11 @@ impl WelcomeSocket {
let mut clients = HashMap::new();

loop {
match self.socket.recv_from(&mut self.buffer).handle_try_again() {
match self
.broadcast_receiver
.recv_from(&mut self.buffer)
.handle_try_again()
{
Ok((size, address)) => {
if size == HANDSHAKE_PACKET_SIZE_BYTES
&& &self.buffer[..ALVR_NAME.len()] == ALVR_NAME.as_bytes()
Expand All @@ -37,11 +50,11 @@ impl WelcomeSocket {
protocol_id_bytes.copy_from_slice(&self.buffer[16..24]);
let received_protocol_id = u64::from_le_bytes(protocol_id_bytes);

if received_protocol_id != alvr_common::protocol_id() {
if received_protocol_id != alvr_common::protocol_id_u64() {
warn!(
"Found incompatible client! Upgrade or downgrade\n{} {}, {} {}",
"Expected protocol ID",
alvr_common::protocol_id(),
alvr_common::protocol_id_u64(),
"Found",
received_protocol_id
);
Expand Down Expand Up @@ -70,6 +83,36 @@ impl WelcomeSocket {
}
}

loop {
match self.mdns_receiver.try_recv() {
Ok(event) => {
if let ServiceEvent::ServiceResolved(info) = event {
let hostname = info.get_hostname();
let address = *info.get_addresses().iter().next().to_any()?;

let protocol = info
.get_property_val_str(alvr_sockets::MDNS_PROTOCOL_KEY)
.to_any()?;

if protocol != alvr_common::protocol_id() {
let msg = format!(
r#"Expected protocol ID "{}", found "{}""#,
alvr_common::protocol_id(),
protocol
);
warn!(
"Found incompatible client {hostname}! Upgrade or downgrade\n{msg}"
);
}

clients.insert(hostname.into(), address);
}
}
Err(TryRecvError::Empty) => break,
Err(e) => bail!(e),
}
}

Ok(clients)
}
}
3 changes: 3 additions & 0 deletions alvr/sockets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub const HANDSHAKE_PACKET_SIZE_BYTES: usize = 56; // this may change in future
pub const KEEPALIVE_INTERVAL: Duration = Duration::from_millis(500);
pub const KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(2);

pub const MDNS_SERVICE_TYPE: &str = "_alvr._tcp.local.";
pub const MDNS_PROTOCOL_KEY: &str = "protocol";

fn set_socket_buffers(
socket: &socket2::Socket,
send_buffer_bytes: SocketBufferSize,
Expand Down
Loading