Compare commits

...

10 Commits

Author SHA1 Message Date
Sashanoraa df55c04e85 Test: Disable adb auth 2025-09-02 15:40:45 -04:00
Sashanoraa 7475cd5cd9 Hide error count in error toast if there is only 1 error 2025-08-28 16:20:16 -07:00
Sashanoraa cef94ba6b0 Add log view modal to web UI 2025-08-28 16:19:17 -07:00
Sashanoraa d7c973ea95 Add api for getting rayhunter's log file 2025-08-28 16:19:17 -07:00
Simon Fondrie-Teitler 64d657efd6 Don't return an error if there's an issue parsing battery status 2025-08-28 16:09:09 -07:00
Simon Fondrie-Teitler 16447ed8bf Use percentage battery in frontend and not levels 2025-08-28 16:09:09 -07:00
Simon Fondrie-Teitler 663d0abb57 Add battery level to web UI 2025-08-28 16:09:09 -07:00
Simon Fondrie-Teitler f49d11f034 Skip updating web UI when browser tab isn't visible
This will save bandwith and battery on the hotspot in cases where the UI
tab is left open. See #564.
2025-08-27 22:52:34 +02:00
Cooper Quintin 56dcfdb47c Update orbic.md 2025-08-26 10:17:54 -07:00
Markus Unterwaditzer a46ede37b6 Fix links in README 2025-08-26 09:22:07 -07:00
21 changed files with 410 additions and 42 deletions
Generated
+1 -2
View File
@@ -5,7 +5,7 @@ version = 4
[[package]]
name = "adb_client"
version = "2.1.11"
source = "git+https://github.com/EFForg/adb_client.git?rev=e511662394e4fa32865c154c40f81a3d846f700c#e511662394e4fa32865c154c40f81a3d846f700c"
source = "git+https://github.com/EFForg/adb_client.git?rev=208a302367727554d7530e937ca8aee20a74fa51#208a302367727554d7530e937ca8aee20a74fa51"
dependencies = [
"async-io",
"base64",
@@ -22,7 +22,6 @@ dependencies = [
"nusb",
"rand 0.9.1",
"regex",
"rsa",
"rusb",
"rustls-pki-types",
"serde",
+4 -4
View File
@@ -3,17 +3,17 @@
![Rayhunter Logo - An Orca taking a bite out of a cellular signal bar](https://www.eff.org/files/styles/media_browser_preview/public/banner_library/rayhunter-banner.png)
Rayhunter is a project for detecting IMSI catchers, also known as cell-site simulators or stingrays. It was first designed to run on a cheap mobile hotspot called the Orbic RC400L, but thanks to community efforts can [support some other devices as well](https://efforg.github.io/rayhunter/supported-devices.md).
Rayhunter is a project for detecting IMSI catchers, also known as cell-site simulators or stingrays. It was first designed to run on a cheap mobile hotspot called the Orbic RC400L, but thanks to community efforts can [support some other devices as well](https://efforg.github.io/rayhunter/supported-devices.html).
It's also designed to be as easy to install and use as possible, regardless of your level of technical skills, and to minimize false positives.
→ Check out the [installation guide](https://efforg.github.io/rayhunter/installation.md) to get started.
→ Check out the [installation guide](https://efforg.github.io/rayhunter/installation.html) to get started.
→ To learn more about the aim of the project, and about IMSI catchers in general, please check out our [introductory blog post](https://www.eff.org/deeplinks/2025/03/meet-rayhunter-new-open-source-tool-eff-detect-cellular-spying).
→ For discussion, help, or to join the mattermost channel and get involved with the project and community check out the [many ways listed here](https://efforg.github.io/rayhunter/support-feedback-community.md)!
→ For discussion, help, or to join the mattermost channel and get involved with the project and community check out the [many ways listed here](https://efforg.github.io/rayhunter/support-feedback-community.html)!
→ To learn more about the project in general check out the [Rayhunter Book](https://efforg.github.io/rayhunter/).
**LEGAL DISCLAIMER:** Use this program at your own risk. We believe running this program does not currently violate any laws or regulations in the United States. However, we are not responsible for civil or criminal liability resulting from the use of this software. If you are located outside of the US please consult with an attorney in your country to help you assess the legal risks of running this program.
*Good Hunting!*
*Good Hunting!*
+47
View File
@@ -0,0 +1,47 @@
use std::path::Path;
use rayhunter::Device;
use serde::Serialize;
use crate::error::RayhunterError;
pub mod orbic;
pub mod tmobile;
pub mod wingtech;
#[derive(Clone, Copy, PartialEq, Debug, Serialize)]
pub struct BatteryState {
level: u8,
is_plugged_in: bool,
}
async fn is_plugged_in_from_file(path: &Path) -> Result<bool, RayhunterError> {
match tokio::fs::read_to_string(path)
.await
.map_err(RayhunterError::TokioError)?
.chars()
.next()
{
Some('0') => Ok(false),
Some('1') => Ok(true),
_ => Err(RayhunterError::BatteryPluggedInStatusParseError),
}
}
async fn get_level_from_percentage_file(path: &Path) -> Result<u8, RayhunterError> {
tokio::fs::read_to_string(path)
.await
.map_err(RayhunterError::TokioError)?
.trim_end()
.parse()
.or(Err(RayhunterError::BatteryLevelParseError))
}
pub async fn get_battery_status(device: &Device) -> Result<BatteryState, RayhunterError> {
Ok(match device {
Device::Orbic => orbic::get_battery_state().await?,
Device::Wingtech => wingtech::get_battery_state().await?,
Device::Tmobile => tmobile::get_battery_state().await?,
_ => return Err(RayhunterError::FunctionNotSupportedForDeviceError),
})
}
+28
View File
@@ -0,0 +1,28 @@
use std::path::Path;
use crate::{
battery::{BatteryState, is_plugged_in_from_file},
error::RayhunterError,
};
const BATTERY_LEVEL_FILE: &str = "/sys/kernel/chg_info/level";
const PLUGGED_IN_STATE_FILE: &str = "/sys/kernel/chg_info/chg_en";
pub async fn get_battery_state() -> Result<BatteryState, RayhunterError> {
Ok(BatteryState {
level: match tokio::fs::read_to_string(&BATTERY_LEVEL_FILE)
.await
.map_err(RayhunterError::TokioError)?
.chars()
.next()
{
Some('1') => Ok(10),
Some('2') => Ok(25),
Some('3') => Ok(50),
Some('4') => Ok(75),
Some('5') => Ok(100),
_ => Err(RayhunterError::BatteryLevelParseError),
}?,
is_plugged_in: is_plugged_in_from_file(Path::new(PLUGGED_IN_STATE_FILE)).await?,
})
}
+16
View File
@@ -0,0 +1,16 @@
use std::path::Path;
use crate::{
battery::{BatteryState, get_level_from_percentage_file, is_plugged_in_from_file},
error::RayhunterError,
};
const BATTERY_LEVEL_FILE: &str = "/sys/class/power_supply/bms/capacity";
const PLUGGED_IN_STATE_FILE: &str = "/sys/devices/78d9000.usb/power_supply/usb/online";
pub async fn get_battery_state() -> Result<BatteryState, RayhunterError> {
Ok(BatteryState {
level: get_level_from_percentage_file(Path::new(BATTERY_LEVEL_FILE)).await?,
is_plugged_in: is_plugged_in_from_file(Path::new(PLUGGED_IN_STATE_FILE)).await?,
})
}
+17
View File
@@ -0,0 +1,17 @@
use std::path::Path;
use crate::{
battery::{BatteryState, get_level_from_percentage_file, is_plugged_in_from_file},
error::RayhunterError,
};
const BATTERY_LEVEL_FILE: &str =
"/sys/devices/78b7000.i2c/i2c-3/3-0063/power_supply/cw2017-bat/capacity";
const PLUGGED_IN_STATE_FILE: &str = "/sys/devices/8a00000.ssusb/power_supply/usb/online";
pub async fn get_battery_state() -> Result<BatteryState, RayhunterError> {
Ok(BatteryState {
level: get_level_from_percentage_file(Path::new(BATTERY_LEVEL_FILE)).await?,
is_plugged_in: is_plugged_in_from_file(Path::new(PLUGGED_IN_STATE_FILE)).await?,
})
}
+6
View File
@@ -15,4 +15,10 @@ pub enum RayhunterError {
QmdlStoreError(#[from] RecordingStoreError),
#[error("No QMDL store found at path {0}, but can't create a new one due to debug mode")]
NoStoreDebugMode(String),
#[error("Error parsing file to determine battery level")]
BatteryLevelParseError,
#[error("Error parsing file to determine whether device is plugged in")]
BatteryPluggedInStatusParseError,
#[error("The requested functionality is not supported for this device")]
FunctionNotSupportedForDeviceError,
}
+3
View File
@@ -1,4 +1,5 @@
mod analysis;
mod battery;
mod config;
mod diag;
mod display;
@@ -39,6 +40,7 @@ use log::{error, info};
use qmdl_store::RecordingStoreError;
use rayhunter::Device;
use rayhunter::diag_device::DiagDevice;
use stats::get_log;
use tokio::net::TcpListener;
use tokio::select;
use tokio::sync::mpsc::{self, Sender};
@@ -55,6 +57,7 @@ fn get_router() -> AppRouter {
.route("/api/zip/{name}", get(get_zip))
.route("/api/system-stats", get(get_system_stats))
.route("/api/qmdl-manifest", get(get_qmdl_manifest))
.route("/api/log", get(get_log))
.route("/api/start-recording", post(start_recording))
.route("/api/stop-recording", post(stop_recording))
.route("/api/delete-recording/{name}", post(delete_recording))
+19 -1
View File
@@ -1,7 +1,9 @@
use std::sync::Arc;
use crate::qmdl_store::ManifestEntry;
use crate::battery::get_battery_status;
use crate::error::RayhunterError;
use crate::server::ServerState;
use crate::{battery::BatteryState, qmdl_store::ManifestEntry};
use axum::Json;
use axum::extract::State;
@@ -16,6 +18,8 @@ pub struct SystemStats {
pub disk_stats: DiskStats,
pub memory_stats: MemoryStats,
pub runtime_metadata: RuntimeMetadata,
#[serde(skip_serializing_if = "Option::is_none")]
pub battery_status: Option<BatteryState>,
}
impl SystemStats {
@@ -24,6 +28,14 @@ impl SystemStats {
disk_stats: DiskStats::new(qmdl_path, device).await?,
memory_stats: MemoryStats::new(device).await?,
runtime_metadata: RuntimeMetadata::new(),
battery_status: match get_battery_status(device).await {
Ok(status) => Some(status),
Err(RayhunterError::FunctionNotSupportedForDeviceError) => None,
Err(err) => {
log::error!("Failed to get battery status: {err}");
None
}
},
})
}
}
@@ -156,3 +168,9 @@ pub async fn get_qmdl_manifest(
current_entry,
}))
}
pub async fn get_log() -> Result<String, (StatusCode, String)> {
tokio::fs::read_to_string("/data/rayhunter/rayhunter.log")
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
}
+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<body data-sveltekit-preload-data="hover" style="width: 100%">
<div style="display: contents" class="m-4 xl:m-8">%sveltekit.body%</div>
</body>
</html>
@@ -43,29 +43,43 @@
Error Completing Action {current_error.times > 1 ? `x${current_error.times}` : ''}
</span>
<div class="flex items-center mb-2">
<span>{pos + 1}/{action_errors.length}</span>
<button title="previous error" aria-label="previous error" onclick={prev_error}>
<svg aria-hidden="true" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m 15.499979,19.499979 -6.9999997,-7 6.9999997,-6.9999997"
/>
</svg>
</button>
<button title="next error" aria-label="next error" onclick={next_error}>
<svg aria-hidden="true" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m 8.5000207,5.4999793 7.0000003,6.9999997 -7.0000003,7"
/>
</svg>
</button>
{#if action_errors.length > 1}
<span>{pos + 1}/{action_errors.length}</span>
<button title="previous error" aria-label="previous error" onclick={prev_error}>
<svg
aria-hidden="true"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m 15.499979,19.499979 -6.9999997,-7 6.9999997,-6.9999997"
/>
</svg>
</button>
<button title="next error" aria-label="next error" onclick={next_error}>
<svg
aria-hidden="true"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m 8.5000207,5.4999793 7.0000003,6.9999997 -7.0000003,7"
/>
</svg>
</button>
{/if}
<button title="clear errors" aria-label="clear errors" onclick={clear_errors}>
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
<path
@@ -0,0 +1,74 @@
<script lang="ts">
import { get_logs } from '$lib/utils.svelte';
import { onMount } from 'svelte';
let { shown = $bindable() }: { shown: boolean } = $props();
let content: string | undefined = $state(undefined);
onMount(() => {
// Used by LogView modal
window.addEventListener('scroll', () => {
document.documentElement.style.setProperty('--scroll-y', `${window.scrollY}px`);
});
});
$effect(() => {
if (shown) {
const scrollY = document.documentElement.style.getPropertyValue('--scroll-y');
const body = document.body;
body.style.position = 'fixed';
body.style.top = `-${scrollY}`;
} else {
const body = document.body;
const scrollY = body.style.top;
body.style.position = '';
body.style.top = '';
window.scrollTo(0, parseInt(scrollY || '0') * -1);
}
const interval = setInterval(async () => {
try {
// Don't update UI if browser tab isn't visible
if (content !== undefined && (document.hidden || !shown)) {
return;
}
content = await get_logs();
} catch (error) {
console.log(error);
}
}, 1000);
return () => clearInterval(interval);
});
</script>
{#if shown}
<div
class="fixed left-5 right-5 top-5 bottom-5 z-50 bg-white border border-white rounded-md
flex flex-col p-2 drop-shadow"
>
<div class="flex h-20 justify-between items-center p-1">
<span class="text-2xl mb-2">Log</span>
<button onclick={() => (shown = false)} aria-label="close">
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166 4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166 19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289 17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z"
fill="#0F1729"
/>
</svg>
</button>
</div>
<div class="bg-gray-100 border border-gray-100 rounded-md overflow-scroll">
<pre class="m-2">{content}</pre>
</div>
</div>
{/if}
@@ -7,6 +7,32 @@
} = $props();
const table_cell_classes = 'border p-1 lg:p-2';
let battery_level = $derived(stats.battery_status ? stats.battery_status.level : 0);
let bar_color = $derived.by(() => {
if (stats.battery_status === undefined) {
return '';
}
if (battery_level <= 10) {
return 'fill-red-500';
}
if (battery_level <= 25) {
return 'fill-yellow-300';
}
return 'fill-green-500';
});
let title_text = $derived.by(() => {
if (stats.battery_status === undefined) {
return 'Rayhunter does not yet support displaying the battery level for this device.';
}
let text = `Battery is ${stats.battery_status.level}% full`;
if (stats.battery_status.is_plugged_in) {
text += ' and plugged in';
}
return text;
});
</script>
<div
@@ -32,6 +58,64 @@
Free: {stats.memory_stats.free}, Used: {stats.memory_stats.used}
</td>
</tr>
<tr class="border-b">
<th class={table_cell_classes}> Battery </th>
<td class={table_cell_classes}>
<svg
width="80"
height="30"
viewBox="0 0 80 30"
role="img"
xmlns="http://www.w3.org/2000/svg"
class="battery-icon"
>
<title>{title_text}</title>
<!-- Battery body -->
<rect
class="fill-none stroke-neutral-800 stroke-2"
width="70"
height="30"
rx="3"
ry="3"
/>
<!-- Battery terminal -->
<rect
class="fill-neutral-800"
x="70"
y="7"
width="8"
height="16"
rx="2"
ry="2"
/>
<!-- Battery charge bar -->
<rect
class={bar_color}
x="2"
y="2"
height="26"
rx="2"
ry="2"
style="width: {battery_level * 0.66}px;"
/>
{#if stats.battery_status && stats.battery_status.is_plugged_in}
<!-- Lightning bolt icon -->
<path
class="fill-yellow-300 stroke-neutral-800 stroke-1"
d="M38 3 L28 17 L34 17 L30 27 L40 13 L34 13 Z"
/>
{/if}
{#if !stats.battery_status}
<!-- Question mark icon -->
<text
class="fill-neutral-500 text-[20px] font-bold [text-anchor:middle] [dominant-baseline:central]"
x="35"
y="15">?</text
>
{/if}
</svg>
</td>
</tr>
</tbody>
</table>
</div>
+6
View File
@@ -2,6 +2,7 @@ export interface SystemStats {
disk_stats: DiskStats;
memory_stats: MemoryStats;
runtime_metadata: RuntimeMetadata;
battery_status?: BatteryStatus;
}
export interface RuntimeMetadata {
@@ -24,3 +25,8 @@ export interface MemoryStats {
used: string;
free: string;
}
export interface BatteryStatus {
level: number;
is_plugged_in: boolean;
}
+4
View File
@@ -58,6 +58,10 @@ export async function get_system_stats(): Promise<SystemStats> {
return JSON.parse(await req('GET', '/api/system-stats'));
}
export async function get_logs(): Promise<string> {
return await req('GET', '/api/log');
}
export async function get_config(): Promise<Config> {
return JSON.parse(await req('GET', '/api/config'));
}
+56 -4
View File
@@ -7,9 +7,10 @@
import { AnalysisManager } from '$lib/analysisManager.svelte';
import SystemStatsTable from '$lib/components/SystemStatsTable.svelte';
import DeleteAllButton from '$lib/components/DeleteAllButton.svelte';
import RecordingControls from '$lib/components//RecordingControls.svelte';
import RecordingControls from '$lib/components/RecordingControls.svelte';
import ConfigForm from '$lib/components/ConfigForm.svelte';
import ActionErrors from '$lib/components/ActionErrors.svelte';
import LogView from '$lib/components/LogView.svelte';
let manager: AnalysisManager = new AnalysisManager();
let loaded = $state(false);
@@ -17,9 +18,15 @@
let current_entry: ManifestEntry | undefined = $state(undefined);
let system_stats: SystemStats | undefined = $state(undefined);
let update_error: string | undefined = $state(undefined);
let logview_shown: boolean = $state(false);
$effect(() => {
const interval = setInterval(async () => {
try {
// Don't update UI if browser tab isn't visible
if (document.hidden) {
return;
}
await manager.update();
let new_manifest = await get_manifest();
await new_manifest.set_analysis_status(manager);
@@ -42,10 +49,55 @@
});
</script>
<LogView bind:shown={logview_shown} />
<div class="p-4 xl:px-8 bg-rayhunter-blue drop-shadow flex flex-row justify-between items-center">
<!-- https://www.w3.org/WAI/tutorials/images/decorative/ -->
<img src="/rayhunter_text.png" alt="" class="h-10 xl:h-12" />
<div class="flex flex-row gap-4">
<button onclick={() => (logview_shown = true)} class="flex flex-row gap-1 group">
<span class="hidden text-white group-hover:text-gray-400 lg:flex">Logs</span>
<svg
class="w-6 h-6 text-white group-hover:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
d="M10 14H3"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
d="M10 18H3"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
d="M14 15L17.5 18L21 15"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3 6L13.5 6M20 6L17.75 6"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
d="M20 10L9.5 10M3 10H5.25"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
</svg>
</button>
<a
class="flex flex-row gap-1 group"
href="https://github.com/EFForg/rayhunter/issues"
@@ -163,9 +215,9 @@
</svg>
WARNING: Not Running
</span>
<span
>Rayhunter is not currently running and will not detect abnormal behavior!</span
>
<span>
Rayhunter is not currently running and will not detect abnormal behavior!
</span>
<div class="flex flex-row justify-end mt-2">
<RecordingControls server_is_recording={!!current_entry} />
</div>
+1 -1
View File
@@ -29,7 +29,7 @@ procedure at `./installer orbic-network` that is supposed to eventually replace
identically on Windows, Mac and Linux. From our testing it works much more
reliably on Windows than `./installer orbic` does.
The drawback is that the device's admin password is required. If you have a Kajeet-branded "SmartSpot" you currently have to use the USB-based `./installer orbic`, as we currently don't know of a way to get that admin password.
The drawback is that the device's admin password is required.
1. Connect to the Orbic's network via WiFi or USB tethering
2. Run `./installer orbic-network`
+2 -2
View File
@@ -26,12 +26,12 @@ tokio-stream = "0.1.17"
[target.'cfg(target_os = "linux")'.dependencies.adb_client]
git = "https://github.com/EFForg/adb_client.git"
rev = "e511662394e4fa32865c154c40f81a3d846f700c"
rev = "208a302367727554d7530e937ca8aee20a74fa51"
default-features = false
features = ["trans-nusb"]
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies.adb_client]
git = "https://github.com/EFForg/adb_client.git"
rev = "e511662394e4fa32865c154c40f81a3d846f700c"
rev = "208a302367727554d7530e937ca8aee20a74fa51"
default-features = false
features = ["trans-libusb"]
+1 -1
View File
@@ -246,7 +246,7 @@ async fn get_adb() -> Result<ADBUSBDevice> {
const MAX_FAILURES: u32 = 10;
let mut failures = 0;
loop {
match ADBUSBDevice::new(VENDOR_ID, PRODUCT_ID) {
match ADBUSBDevice::new_no_auth(VENDOR_ID, PRODUCT_ID) {
Ok(dev) => match adb_echo_test(dev).await {
Ok(dev) => return Ok(dev),
Err(e) => {
+2 -2
View File
@@ -22,7 +22,7 @@ pub async fn install() -> Result<()> {
echo!("Unlocking modem ... ");
start_adb().await?;
sleep(Duration::from_secs(3)).await;
let mut adb = ADBUSBDevice::new(USB_VENDOR_ID, USB_PRODUCT_ID).unwrap();
let mut adb = ADBUSBDevice::new_no_auth(USB_VENDOR_ID, USB_PRODUCT_ID).unwrap();
println!("ok");
adb.run_command(&["mount", "-o", "remount,rw", "/"], "exit code 0")?;
@@ -57,7 +57,7 @@ pub async fn install() -> Result<()> {
echo!("Unlocking modem ... ");
start_adb().await?;
sleep(Duration::from_secs(3)).await;
let mut adb = ADBUSBDevice::new(USB_VENDOR_ID, USB_PRODUCT_ID).unwrap();
let mut adb = ADBUSBDevice::new_no_auth(USB_VENDOR_ID, USB_PRODUCT_ID).unwrap();
println!("ok");
echo!("Testing rayhunter ... ");
+1 -1
View File
@@ -94,7 +94,7 @@ async fn wait_for_adb() -> Result<ADBUSBDevice> {
// UZ801 USB vendor and product IDs.
// TODO: Research if other variants use different IDs.
match ADBUSBDevice::new(0x05c6, 0x90b6) {
match ADBUSBDevice::new_no_auth(0x05c6, 0x90b6) {
Ok(mut device) => {
// Test ADB connection
if test_adb_connection(&mut device).await.is_ok() {