[Fix] Stability
This commit is contained in:
parent
6b4c144b73
commit
14df716cf1
19 changed files with 483 additions and 405 deletions
|
|
@ -1,2 +1,2 @@
|
|||
[env]
|
||||
MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true }
|
||||
MTP_TYPE_MAPS = { value = "mtp-type-maps/type-maps.yaml", relative = true }
|
||||
|
|
|
|||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "mtp-type-maps"]
|
||||
path = mtp-type-maps
|
||||
url = ssh://git@git.methanium.net/tensamin/mtp-type-maps
|
||||
38
Cargo.lock
generated
38
Cargo.lock
generated
|
|
@ -198,7 +198,7 @@ dependencies = [
|
|||
"foldhash",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"impl-more 0.3.2",
|
||||
"impl-more 0.3.5",
|
||||
"itoa",
|
||||
"language-tags",
|
||||
"log",
|
||||
|
|
@ -651,9 +651,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
|
||||
checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
|
|
@ -1327,9 +1327,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.16.0"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
|
|
@ -2136,9 +2136,9 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
|||
|
||||
[[package]]
|
||||
name = "impl-more"
|
||||
version = "0.3.2"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134d2c4324d61664107020b79019cf6a6aec153f0b79bc9619ee9e794a5fb021"
|
||||
checksum = "277ff51754a3f68f12f58446c5d006aa8baa4914ea273cce24a599cfaff33d4f"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
|
|
@ -2956,7 +2956,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"mtp-client",
|
||||
"mtp-codec",
|
||||
|
|
@ -2972,7 +2972,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-client"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
|
|
@ -2985,7 +2985,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-codec"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
|
|
@ -2998,7 +2998,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-common"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"quinn",
|
||||
"rustls",
|
||||
|
|
@ -3009,7 +3009,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-crypto"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chacha20poly1305",
|
||||
|
|
@ -3031,7 +3031,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-files"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"mtp-crypto",
|
||||
"rand 0.10.2",
|
||||
|
|
@ -3042,7 +3042,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-host"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
|
|
@ -3057,7 +3057,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-transport"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"mtp-codec",
|
||||
|
|
@ -3075,7 +3075,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-type-map"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
|
|
@ -3084,7 +3084,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-webserver"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#00f0aaeeff802c716f143fefe0117470cc5a1738"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#88ae866b91857c9b1650ea0ee2af2fdb70b92f10"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
|
@ -4263,9 +4263,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.15.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
||||
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
|
|
|
|||
|
|
@ -594,7 +594,7 @@ impl IpcClient {
|
|||
return Err(error);
|
||||
}
|
||||
|
||||
match tokio::time::timeout(Duration::from_secs(30), response_rx).await {
|
||||
match tokio::time::timeout(Duration::from_secs(45), response_rx).await {
|
||||
Ok(Ok(result)) => Ok(result),
|
||||
Ok(Err(_)) => Ok(ResponseResult::Error(iota_ipc::IpcErrorCode::Disconnected)),
|
||||
Err(_) => {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,17 @@ impl OverviewScreen {
|
|||
|
||||
lines.push(Line::from(Span::styled("Connection", theme.text.heading)));
|
||||
lines.push(Line::from(format!(" State: {}", connection_label(&conn))));
|
||||
let omikron = daemon.components.get(&iota_ipc::ComponentId::Omikron);
|
||||
let omikron_label = match omikron.map(|health| health.status) {
|
||||
Some(iota_ipc::HealthStatus::Healthy) => "[OK] Connected",
|
||||
Some(iota_ipc::HealthStatus::Degraded) => "[WARN] Connecting or unavailable",
|
||||
Some(iota_ipc::HealthStatus::Failed) => "[FAIL] Authentication failed",
|
||||
None => "Unknown",
|
||||
};
|
||||
lines.push(Line::from(format!(" Omikron: {omikron_label}")));
|
||||
if let Some(message) = omikron.and_then(|health| health.message.as_deref()) {
|
||||
lines.push(Line::from(format!(" Omikron detail: {message}")));
|
||||
}
|
||||
lines.push(Line::from(""));
|
||||
|
||||
lines.push(Line::from(Span::styled("Daemon", theme.text.heading)));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use iota_ipc::{
|
|||
};
|
||||
use iota_logger::{log, log_command};
|
||||
use iota_storage::users::user_manager;
|
||||
use iota_storage::util::config_util::{self, modify_config};
|
||||
use iota_storage::util::config_util::{self};
|
||||
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
|
@ -52,10 +52,7 @@ impl CommandRouter {
|
|||
}
|
||||
let needs_omikron = matches!(
|
||||
request,
|
||||
LocalRequest::CreateUser { .. }
|
||||
| LocalRequest::RemoveUser { .. }
|
||||
| LocalRequest::ReconnectOmikron
|
||||
| LocalRequest::RotateIotaIdentity
|
||||
LocalRequest::CreateUser { .. } | LocalRequest::RemoveUser { .. }
|
||||
);
|
||||
if needs_omikron && !self.services.omikron.is_connected().await {
|
||||
return ResponseResult::Error(
|
||||
|
|
@ -112,11 +109,33 @@ impl CommandRouter {
|
|||
)
|
||||
.await
|
||||
{
|
||||
(Some(user), _) => ResponseResult::Ok(ResponsePayload::UserCreated {
|
||||
Ok(user) => ResponseResult::Ok(ResponsePayload::UserCreated {
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
}),
|
||||
_ => ResponseResult::Error(IpcErrorCode::StorageFailure),
|
||||
Err(error) => {
|
||||
log!("User creation failed: {error:?}");
|
||||
match error {
|
||||
omikron_connector::user_ops::CreateUserError::InvalidUsername => {
|
||||
ResponseResult::Error(IpcErrorCode::InvalidRequest)
|
||||
}
|
||||
omikron_connector::user_ops::CreateUserError::Transport(
|
||||
omikron_connector::OmikronError::Timeout(_),
|
||||
) => ResponseResult::Error(IpcErrorCode::Timeout),
|
||||
omikron_connector::user_ops::CreateUserError::Transport(_) => {
|
||||
ResponseResult::Error(IpcErrorCode::OmikronUnavailable)
|
||||
}
|
||||
omikron_connector::user_ops::CreateUserError::RemoteRejected => {
|
||||
ResponseResult::Error(IpcErrorCode::Conflict)
|
||||
}
|
||||
omikron_connector::user_ops::CreateUserError::LocalPersistence(_) => {
|
||||
ResponseResult::Error(IpcErrorCode::StorageFailure)
|
||||
}
|
||||
omikron_connector::user_ops::CreateUserError::InvalidResponse => {
|
||||
ResponseResult::Error(IpcErrorCode::InternalFailure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LocalRequest::RemoveUser { user_id } => {
|
||||
|
|
@ -139,16 +158,14 @@ impl CommandRouter {
|
|||
Err(_) => ResponseResult::Error(IpcErrorCode::OmikronUnavailable),
|
||||
},
|
||||
LocalRequest::RotateIotaIdentity => {
|
||||
modify_config(|config| {
|
||||
config.public_key = None;
|
||||
config.private_key = None;
|
||||
config.iota_id = None;
|
||||
});
|
||||
match self.services.omikron.reconnect().await {
|
||||
match self.services.omikron.rotate_identity().await {
|
||||
Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: "Key pair regenerated and Omikron reconnection requested".into(),
|
||||
message: "New identity registered with Omikron".into(),
|
||||
}),
|
||||
Err(_) => ResponseResult::Error(IpcErrorCode::OmikronUnavailable),
|
||||
Err(error) => {
|
||||
log!("Iota identity rotation failed: {}", error);
|
||||
ResponseResult::Error(IpcErrorCode::OmikronUnavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
LocalRequest::RequestProcessExit { intent } => {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ impl OmikronClient for InactiveOmikron {
|
|||
"terms have not been accepted".into(),
|
||||
))
|
||||
}
|
||||
async fn rotate_identity(&self) -> Result<(), OmikronError> {
|
||||
Err(OmikronError::Disconnected(
|
||||
"terms have not been accepted".into(),
|
||||
))
|
||||
}
|
||||
async fn is_connected(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,12 @@ impl OmikronClient for FakeOmikron {
|
|||
self.reconnects.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
async fn rotate_identity(&self) -> Result<(), OmikronError> {
|
||||
self.reconnects.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
async fn is_connected(&self) -> bool {
|
||||
true
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +47,7 @@ async fn reconnect_uses_the_injected_client() {
|
|||
omikron: fake.clone(),
|
||||
users: Default::default(),
|
||||
config: Default::default(),
|
||||
active: true,
|
||||
});
|
||||
let router = CommandRouter::new(
|
||||
Arc::new(DaemonRuntime::new()),
|
||||
|
|
@ -55,3 +60,29 @@ async fn reconnect_uses_the_injected_client() {
|
|||
));
|
||||
assert_eq!(fake.reconnects.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn identity_rotation_is_available_while_omikron_is_offline() {
|
||||
let fake = Arc::new(FakeOmikron {
|
||||
reconnects: AtomicUsize::new(0),
|
||||
});
|
||||
let services = Arc::new(DaemonServices {
|
||||
omikron: fake.clone(),
|
||||
users: Default::default(),
|
||||
config: Default::default(),
|
||||
active: true,
|
||||
});
|
||||
let router = CommandRouter::new(
|
||||
Arc::new(DaemonRuntime::new()),
|
||||
services,
|
||||
Arc::new(Mutex::new(LogBuffer::new(100))),
|
||||
);
|
||||
assert!(matches!(
|
||||
router
|
||||
.route(1, LocalRequest::RotateIotaIdentity)
|
||||
.await
|
||||
.result,
|
||||
ResponseResult::Ok(_)
|
||||
));
|
||||
assert_eq!(fake.reconnects.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,19 +140,75 @@ async fn main() -> ExitCode {
|
|||
);
|
||||
connection
|
||||
}
|
||||
Err(omikron_connector::OmikronStartupError::Authentication) => {
|
||||
Err(omikron_connector::OmikronStartupError::Authentication { connection }) => {
|
||||
runtime.set_component_failed(
|
||||
iota_ipc::ComponentId::Omikron,
|
||||
"Omikron authentication failed".into(),
|
||||
"Omikron authentication failed; regenerate the Iota identity to register again"
|
||||
.into(),
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
// Keep IPC alive: identity rotation is the supported recovery
|
||||
// action and must remain available after authentication fails.
|
||||
connection
|
||||
}
|
||||
Err(omikron_connector::OmikronStartupError::Construction(error)) => {
|
||||
eprintln!("Cannot construct Omikron connection: {error}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
let omikron_health = omikron.clone();
|
||||
let services = DaemonServices::new(omikron);
|
||||
let health_runtime = runtime.clone();
|
||||
runtime
|
||||
.tasks
|
||||
.spawn_tracked("omikron-health", async move {
|
||||
let mut states = omikron_health.connection_state();
|
||||
loop {
|
||||
let state = *states.borrow();
|
||||
match state {
|
||||
omikron_connector::omikron_connection::ConnectionState::Connected {
|
||||
..
|
||||
} => {
|
||||
let ping_ms = *omikron_health.last_ping.lock().await;
|
||||
let message = if ping_ms >= 0 {
|
||||
format!("connected (RTT: {ping_ms} ms)")
|
||||
} else {
|
||||
"connected (waiting for RTT sample)".into()
|
||||
};
|
||||
health_runtime
|
||||
.set_component_healthy(iota_ipc::ComponentId::Omikron, Some(message));
|
||||
}
|
||||
omikron_connector::omikron_connection::ConnectionState::Connecting => {
|
||||
health_runtime.set_component_degraded(
|
||||
iota_ipc::ComponentId::Omikron,
|
||||
"connecting to Omikron".into(),
|
||||
);
|
||||
}
|
||||
omikron_connector::omikron_connection::ConnectionState::Disconnected => {
|
||||
let message = omikron_health
|
||||
.get_auth_failure()
|
||||
.await
|
||||
.unwrap_or_else(|| "disconnected; retrying".into());
|
||||
if omikron_health.has_auth_failure().await {
|
||||
health_runtime
|
||||
.set_component_failed(iota_ipc::ComponentId::Omikron, message);
|
||||
} else {
|
||||
health_runtime
|
||||
.set_component_degraded(iota_ipc::ComponentId::Omikron, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::select! {
|
||||
changed = states.changed() => if changed.is_err() { break },
|
||||
// RTT is updated by MTP's heartbeat independently of a
|
||||
// connection-state transition, so periodically refresh
|
||||
// the component detail while connected.
|
||||
_ = tokio::time::sleep(Duration::from_secs(1)) => {},
|
||||
_ = health_runtime.cancellation.cancelled() => break,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
let ipc_server = match IpcServer::bind(
|
||||
socket.clone(),
|
||||
runtime.clone(),
|
||||
|
|
|
|||
|
|
@ -171,5 +171,3 @@ fn run(program: &str, args: &[&str]) -> Result<()> {
|
|||
bail!("{program} failed; run the installer as root")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -98,19 +98,19 @@ pub fn startup_with_log_dir(log_dir: Option<std::path::PathBuf>) {
|
|||
format!("{} ", msg.prefix)
|
||||
};
|
||||
|
||||
let line1 = format!(
|
||||
"{} {}{}",
|
||||
let line = format!(
|
||||
"{} {} {}{}",
|
||||
fixed_box(&msg.timestamp_ms.to_string(), 13),
|
||||
timestamp,
|
||||
prefix,
|
||||
resolved_message
|
||||
);
|
||||
let line2 = format!(" {}", timestamp);
|
||||
|
||||
if let Some(file) = file.as_mut() {
|
||||
let _ = writeln!(file, "{}\n{}", line1, line2);
|
||||
let _ = writeln!(file, "{}", line);
|
||||
}
|
||||
|
||||
let _ = writeln!(std::io::stderr(), "{}\n{}", line1, line2);
|
||||
let _ = writeln!(std::io::stderr(), "{}", line);
|
||||
|
||||
let entry = UiLogEntry {
|
||||
timestamp_ms: msg.timestamp_ms,
|
||||
|
|
@ -329,8 +329,11 @@ pub fn format_cv(cv: &CommunicationValue) -> String {
|
|||
parts.push(format!("> {}", receiver));
|
||||
}
|
||||
|
||||
let comm_type = cv.get_type().to_string();
|
||||
parts.push(format!("{}", comm_type));
|
||||
let comm_type = cv
|
||||
.get_comm_type_enum()
|
||||
.map(|kind| kind.to_string())
|
||||
.unwrap_or_else(|| cv.get_type().to_string());
|
||||
parts.push(format!("{} (id={})", comm_type, cv.get_id()));
|
||||
|
||||
let data = cv.data();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,13 @@ use rand_core::{OsRng, RngCore};
|
|||
use rusqlite::params;
|
||||
|
||||
pub fn add_user(user: UserProfile) {
|
||||
if let Err(e) = db::with_db(|conn| {
|
||||
if let Err(e) = try_add_user(user) {
|
||||
eprintln!("Failed to add_user: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_add_user(user: UserProfile) -> Result<(), crate::storage_error::StorageError> {
|
||||
db::with_db(|conn| {
|
||||
conn.execute(
|
||||
r#"
|
||||
INSERT INTO users (user_id, username, public_key, private_key_hash, reset_token, created_at, display_name)
|
||||
|
|
@ -40,9 +46,7 @@ pub fn add_user(user: UserProfile) {
|
|||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}) {
|
||||
eprintln!("Failed to add_user: {}", e);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_user(user: UserProfile) {
|
||||
|
|
|
|||
|
|
@ -239,5 +239,3 @@ fn pending_forward_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Pending
|
|||
created_at: row.get(8)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -105,41 +105,28 @@ pub fn load_file_vec(path: &str, name: &str) -> Result<Vec<u8>, std::io::Error>
|
|||
}
|
||||
|
||||
pub fn save_file(path: &str, name: &str, value: &str) {
|
||||
let Ok(file_path) = storage_file(path, name) else {
|
||||
eprintln!("[IMPORTANT] Refusing unsafe storage path");
|
||||
return;
|
||||
};
|
||||
let Some(dir) = file_path.parent() else {
|
||||
return;
|
||||
};
|
||||
if let Err(error) = try_save_file(path, name, value) {
|
||||
eprintln!("[IMPORTANT] Couldn't save file: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
if !dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
println!("[IMPORTANT] Couldn't create directories: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pub fn try_save_file(path: &str, name: &str, value: &str) -> io::Result<()> {
|
||||
let file_path = storage_file(path, name)?;
|
||||
let dir = file_path
|
||||
.parent()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "file has no parent"))?;
|
||||
|
||||
fs::create_dir_all(dir)?;
|
||||
|
||||
// Write to a temp file first, then atomically rename to prevent partial writes.
|
||||
let tmp_name = format!(".{}.tmp", name);
|
||||
let tmp_path = dir.join(&tmp_name);
|
||||
if let Err(e) = fs::write(&tmp_path, value) {
|
||||
println!(
|
||||
"[IMPORTANT] Couldn't write temp file {}: {}",
|
||||
tmp_path.display(),
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let Err(e) = fs::rename(&tmp_path, &file_path) {
|
||||
println!(
|
||||
"[IMPORTANT] Couldn't rename {} to {}: {}",
|
||||
tmp_path.display(),
|
||||
file_path.display(),
|
||||
e
|
||||
);
|
||||
fs::write(&tmp_path, value)?;
|
||||
if let Err(error) = fs::rename(&tmp_path, &file_path) {
|
||||
let _ = fs::remove_file(&tmp_path);
|
||||
return Err(error);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_children(path: &str) -> Vec<String> {
|
||||
|
|
|
|||
1
mtp-type-maps
Submodule
1
mtp-type-maps
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 594646ac39d986f0787aa614a99d580035a67318
|
||||
|
|
@ -27,7 +27,9 @@ pub enum OmikronStartupError {
|
|||
InitialConnectionTimeout {
|
||||
connection: std::sync::Arc<crate::omikron_connection::OmikronConnection>,
|
||||
},
|
||||
Authentication,
|
||||
Authentication {
|
||||
connection: std::sync::Arc<crate::omikron_connection::OmikronConnection>,
|
||||
},
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
|
@ -39,5 +41,9 @@ pub trait OmikronClient: Send + Sync {
|
|||
timeout: Duration,
|
||||
) -> Result<CommunicationValue, OmikronError>;
|
||||
async fn reconnect(&self) -> Result<(), OmikronError>;
|
||||
/// Replace the local Iota identity and wait for the new identity to
|
||||
/// register/authenticate. This is deliberately available while offline:
|
||||
/// it is the recovery operation for an authentication failure.
|
||||
async fn rotate_identity(&self) -> Result<(), OmikronError>;
|
||||
async fn is_connected(&self) -> bool;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use std::env;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use tokio::sync::{Mutex, RwLock, Semaphore, oneshot, watch};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::sleep;
|
||||
|
|
@ -221,6 +221,11 @@ impl OmikronConnection {
|
|||
let _ = self.state_watch_tx.send(new_state);
|
||||
}
|
||||
|
||||
/// Subscribe to connection transitions for daemon health reporting.
|
||||
pub fn connection_state(&self) -> watch::Receiver<ConnectionState> {
|
||||
self.state_watch_tx.subscribe()
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Connection Management
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
@ -1880,10 +1885,11 @@ impl OmikronConnection {
|
|||
let reason = response_cv
|
||||
.get_data(DataType::Message)
|
||||
.as_str()
|
||||
.or_else(|| response_cv.get_data(DataType::ErrorType).as_str())
|
||||
.unwrap_or("connection error")
|
||||
.to_string();
|
||||
Err(format!(
|
||||
"Request failed due to disconnect (msg_id={}, reason={})",
|
||||
"Request rejected (msg_id={}, reason={})",
|
||||
msg_id, reason
|
||||
))
|
||||
} else {
|
||||
|
|
@ -1955,6 +1961,75 @@ impl OmikronConnection {
|
|||
self.stop().await;
|
||||
self.connect().await;
|
||||
}
|
||||
|
||||
/// Create a new local keyring and register it as a new Iota identity.
|
||||
/// The existing keyring is retained as a timestamped backup so a failed
|
||||
/// recovery does not silently destroy the user's previous identity.
|
||||
pub async fn rotate_identity(self: &Arc<Self>) -> Result<(), OmikronError> {
|
||||
log!("Iota identity rotation requested");
|
||||
self.stop().await;
|
||||
|
||||
let path = identity_path();
|
||||
if path.exists() {
|
||||
let stamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis();
|
||||
let backup = path.with_extension(format!("mk.backup-{stamp}"));
|
||||
std::fs::rename(path, &backup).map_err(|error| {
|
||||
OmikronError::Internal(format!(
|
||||
"could not back up identity {}: {error}",
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
log!("Existing Iota identity backed up to {}", backup.display());
|
||||
}
|
||||
|
||||
let keyring = crypto_helper::generate_keyring();
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|error| {
|
||||
OmikronError::Internal(format!(
|
||||
"could not create identity directory {}: {error}",
|
||||
parent.display()
|
||||
))
|
||||
})?;
|
||||
}
|
||||
mtp::files::save_keyring_raw(&keyring, path).map_err(|error| {
|
||||
OmikronError::Internal(format!(
|
||||
"could not save new identity {}: {error}",
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
modify_config(|config| {
|
||||
config.iota_id = None;
|
||||
config.keyring = None;
|
||||
config.public_key = None;
|
||||
config.private_key = None;
|
||||
});
|
||||
log!("New Iota identity generated; registration started");
|
||||
|
||||
self.clear_auth_failure().await;
|
||||
self.connect().await;
|
||||
match self.await_connection(Some(CONNECTION_TIMEOUT)).await {
|
||||
Ok(()) => {
|
||||
let id = CONFIG.load().iota_id;
|
||||
log!(
|
||||
"New Iota identity registered{}",
|
||||
id.map(|v| format!(" (Iota-ID: {v})")).unwrap_or_default()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(timeout) => {
|
||||
if let Some(reason) = self.get_auth_failure().await {
|
||||
log!("Iota identity registration failed: {}", reason);
|
||||
Err(OmikronError::Authentication(reason))
|
||||
} else {
|
||||
log!("Iota identity registration did not complete: {}", timeout);
|
||||
Err(OmikronError::Timeout(timeout))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -1975,7 +2050,7 @@ pub async fn connect_initial(
|
|||
match conn.await_connection(Some(CONNECTION_TIMEOUT)).await {
|
||||
Ok(()) => Ok(conn),
|
||||
Err(_) if conn.has_auth_failure().await => {
|
||||
Err(crate::client::OmikronStartupError::Authentication)
|
||||
Err(crate::client::OmikronStartupError::Authentication { connection: conn })
|
||||
}
|
||||
Err(_) => {
|
||||
Err(crate::client::OmikronStartupError::InitialConnectionTimeout { connection: conn })
|
||||
|
|
@ -2027,6 +2102,8 @@ impl OmikronClient for OmikronConnection {
|
|||
.map_err(|error| {
|
||||
if error.contains("timed out") {
|
||||
OmikronError::Timeout(error)
|
||||
} else if error.starts_with("Request rejected") {
|
||||
OmikronError::Internal(error)
|
||||
} else {
|
||||
OmikronError::Disconnected(error)
|
||||
}
|
||||
|
|
@ -2057,6 +2134,29 @@ impl OmikronClient for OmikronConnection {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn rotate_identity(&self) -> Result<(), OmikronError> {
|
||||
let this = Arc::new(Self {
|
||||
state: self.state.clone(),
|
||||
state_watch_tx: self.state_watch_tx.clone(),
|
||||
sender: self.sender.clone(),
|
||||
connection_loop_handle: self.connection_loop_handle.clone(),
|
||||
last_ping: self.last_ping.clone(),
|
||||
heartbeat_handle: self.heartbeat_handle.clone(),
|
||||
connection_id: self.connection_id,
|
||||
shutdown_tx: self.shutdown_tx.clone(),
|
||||
reconnect_on_close: self.reconnect_on_close.clone(),
|
||||
auth_failure: self.auth_failure.clone(),
|
||||
app_challenges: self.app_challenges.clone(),
|
||||
app_sessions: self.app_sessions.clone(),
|
||||
missed_pongs: self.missed_pongs.clone(),
|
||||
handler_semaphore: self.handler_semaphore.clone(),
|
||||
cancellation: self.cancellation.clone(),
|
||||
active_tasks: self.active_tasks.clone(),
|
||||
app: self.app.clone(),
|
||||
});
|
||||
Self::rotate_identity(&this).await
|
||||
}
|
||||
|
||||
async fn is_connected(&self) -> bool {
|
||||
Self::is_connected(self).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use iota_logger::{PrintType, log, log_cv, log_t};
|
||||
use iota_storage::users::user_manager::{add_user, save_users};
|
||||
use iota_storage::users::user_manager::try_add_user;
|
||||
use iota_storage::users::user_profile::UserProfile;
|
||||
use iota_util::crypto_helper::{self, hex_hash, public_key_bundle_to_base64};
|
||||
use iota_util::file_util::save_file;
|
||||
use iota_util::file_util::try_save_file;
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
use rand_core::{OsRng, RngCore};
|
||||
use std::time::Duration;
|
||||
|
|
@ -11,34 +11,75 @@ use std::time::Duration;
|
|||
use crate::OmikronClient;
|
||||
use crate::omega_discovery;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CreateUserError {
|
||||
InvalidUsername,
|
||||
Transport(crate::OmikronError),
|
||||
InvalidResponse,
|
||||
RemoteRejected,
|
||||
LocalPersistence(String),
|
||||
}
|
||||
|
||||
fn valid_username(username: &str) -> bool {
|
||||
!username.is_empty()
|
||||
&& username.chars().count() <= 15
|
||||
&& !username.chars().any(char::is_control)
|
||||
&& !username.contains(['/', '\\'])
|
||||
}
|
||||
|
||||
async fn request_user_id(connection: &dyn OmikronClient) -> Result<(i64, String), CreateUserError> {
|
||||
let request = CommunicationValue::new(CommunicationType::GetRegister);
|
||||
let response = connection
|
||||
.await_response(&request, Duration::from_secs(20))
|
||||
.await
|
||||
.map_err(CreateUserError::Transport)?;
|
||||
|
||||
if !response.is_type(CommunicationType::GetRegister) {
|
||||
return Err(CreateUserError::InvalidResponse);
|
||||
}
|
||||
|
||||
let user_id = response
|
||||
.get_data(DataType::UserId)
|
||||
.as_number()
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
.filter(|id| (1..(1_i64 << 48)).contains(id))
|
||||
.ok_or(CreateUserError::InvalidResponse)?;
|
||||
let registration_token = response
|
||||
.get_data(DataType::RegisterId)
|
||||
.as_str()
|
||||
.filter(|token| uuid::Uuid::parse_str(token).is_ok())
|
||||
.map(str::to_owned)
|
||||
.ok_or(CreateUserError::InvalidResponse)?;
|
||||
Ok((user_id, registration_token))
|
||||
}
|
||||
|
||||
/// A completion response can be lost after Omega commits the user. Confirm
|
||||
/// the exact remote record before treating that transport failure as success.
|
||||
async fn registration_committed(connection: &dyn OmikronClient, profile: &UserProfile) -> bool {
|
||||
let request = CommunicationValue::new(CommunicationType::GetUserData).add_typed_default(
|
||||
DataType::UserId,
|
||||
DataValue::SignedNumber(profile.user_id.into()),
|
||||
);
|
||||
let Ok(response) = connection
|
||||
.await_response(&request, Duration::from_secs(5))
|
||||
.await
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
response.get_data(DataType::UserId).as_number() == Some(profile.user_id.into())
|
||||
&& response.get_data(DataType::Username).as_str() == Some(profile.username.as_str())
|
||||
&& response.get_data(DataType::PublicKey).as_str() == Some(profile.public_key.as_str())
|
||||
}
|
||||
|
||||
pub async fn create_user(
|
||||
connection: &dyn OmikronClient,
|
||||
username: &str,
|
||||
) -> (Option<UserProfile>, Option<String>) {
|
||||
let register_communication_value = CommunicationValue::new(CommunicationType::GetRegister);
|
||||
|
||||
let response_communication_value = match connection
|
||||
.await_response(®ister_communication_value, Duration::from_secs(20))
|
||||
.await
|
||||
{
|
||||
Ok(communication_value) => communication_value,
|
||||
Err(e) => {
|
||||
log_t!("User creation: {}", e.to_string());
|
||||
return (None, None);
|
||||
) -> Result<UserProfile, CreateUserError> {
|
||||
if !valid_username(username) {
|
||||
return Err(CreateUserError::InvalidUsername);
|
||||
}
|
||||
};
|
||||
log_cv!(PrintType::Omega, response_communication_value);
|
||||
|
||||
let user_id = match response_communication_value
|
||||
.get_data(DataType::UserId)
|
||||
.as_number()
|
||||
{
|
||||
Some(id) => id,
|
||||
None => {
|
||||
log_t!("User creation: Response returned none");
|
||||
return (None, None);
|
||||
}
|
||||
};
|
||||
let (user_id, registration_token) = request_user_id(connection).await?;
|
||||
log!("User creation: Omega allocated user ID {user_id}");
|
||||
let keyring = crypto_helper::generate_keyring();
|
||||
let pub_key_bundle = keyring.public_key_bundle();
|
||||
let keyring_b64 = crypto_helper::keyring_to_base64(&keyring);
|
||||
|
|
@ -50,7 +91,7 @@ pub async fn create_user(
|
|||
let reset_token = STANDARD.encode(&bytes);
|
||||
|
||||
let user_profile = UserProfile::new(
|
||||
user_id as i64,
|
||||
user_id,
|
||||
username.to_string(),
|
||||
None,
|
||||
public_key_bundle_to_base64(&pub_key_bundle),
|
||||
|
|
@ -59,30 +100,43 @@ pub async fn create_user(
|
|||
);
|
||||
|
||||
let communication_value = CommunicationValue::new(CommunicationType::CompleteRegisterUser)
|
||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id as i128))
|
||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
|
||||
.add_typed_default(DataType::Username, DataValue::Str(username.to_string()))
|
||||
.add_typed_default(
|
||||
DataType::PublicKey,
|
||||
DataValue::Str(public_key_bundle_to_base64(&pub_key_bundle)),
|
||||
)
|
||||
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(user_id as i128))
|
||||
.add_typed_default(DataType::ResetToken, DataValue::Str(reset_token));
|
||||
.add_typed_default(DataType::ResetToken, DataValue::Str(reset_token))
|
||||
.add_typed_default(DataType::RegisterId, DataValue::Str(registration_token));
|
||||
|
||||
let response_communication_value = connection
|
||||
.await_response(&communication_value, Duration::from_secs(20))
|
||||
.await;
|
||||
|
||||
if let Ok(response) = response_communication_value {
|
||||
match response_communication_value {
|
||||
Ok(response) => {
|
||||
log_cv!(PrintType::Omega, response);
|
||||
if !response.is_type(CommunicationType::Success) {
|
||||
return (None, None);
|
||||
return Err(CreateUserError::RemoteRejected);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
if registration_committed(connection, &user_profile).await {
|
||||
log!(
|
||||
"User creation: completion response was lost; verified user {} remotely",
|
||||
user_id
|
||||
);
|
||||
} else {
|
||||
log_t!("User creation: Response returned none");
|
||||
return (None, None);
|
||||
log_t!("User creation: {}", error.to_string());
|
||||
return Err(match error {
|
||||
crate::OmikronError::Internal(_) => CreateUserError::RemoteRejected,
|
||||
error => CreateUserError::Transport(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
log!("Created User");
|
||||
save_file(
|
||||
try_save_file(
|
||||
"",
|
||||
&format!("{}.tu", username),
|
||||
&format!(
|
||||
|
|
@ -91,9 +145,91 @@ pub async fn create_user(
|
|||
omega_discovery::omega_host(),
|
||||
keyring_b64
|
||||
),
|
||||
);
|
||||
)
|
||||
.map_err(|error| CreateUserError::LocalPersistence(error.to_string()))?;
|
||||
|
||||
add_user(user_profile.clone());
|
||||
save_users();
|
||||
(Some(user_profile), Some(keyring_b64))
|
||||
try_add_user(user_profile.clone())
|
||||
.map_err(|error| CreateUserError::LocalPersistence(error.to_string()))?;
|
||||
Ok(user_profile)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CreateUserError, request_user_id, valid_username};
|
||||
use crate::{OmikronClient, OmikronError};
|
||||
use async_trait::async_trait;
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
use std::time::Duration;
|
||||
|
||||
struct RegistrationClient {
|
||||
response: CommunicationValue,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OmikronClient for RegistrationClient {
|
||||
async fn send_message(&self, _: &CommunicationValue) -> Result<(), OmikronError> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn await_response(
|
||||
&self,
|
||||
request: &CommunicationValue,
|
||||
_: Duration,
|
||||
) -> Result<CommunicationValue, OmikronError> {
|
||||
assert!(request.is_type(CommunicationType::GetRegister));
|
||||
Ok(self.response.clone().with_id(request.get_id()))
|
||||
}
|
||||
|
||||
async fn reconnect(&self) -> Result<(), OmikronError> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn rotate_identity(&self) -> Result<(), OmikronError> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn is_connected(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_usernames_before_remote_registration() {
|
||||
assert!(valid_username("alice"));
|
||||
assert!(valid_username("fifteen_char_ok"));
|
||||
assert!(!valid_username(""));
|
||||
assert!(!valid_username("sixteen_chars_bad"));
|
||||
assert!(!valid_username("path/name"));
|
||||
assert!(!valid_username("line\nbreak"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn uses_user_id_allocated_by_omega() {
|
||||
let client = RegistrationClient {
|
||||
response: CommunicationValue::new(CommunicationType::GetRegister)
|
||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(4_294_967_311))
|
||||
.add_typed_default(
|
||||
DataType::RegisterId,
|
||||
DataValue::Str("00000000-0000-4000-8000-000000000001".into()),
|
||||
),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
request_user_id(&client).await.unwrap(),
|
||||
(4_294_967_311, "00000000-0000-4000-8000-000000000001".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejects_registration_response_without_a_positive_user_id() {
|
||||
let client = RegistrationClient {
|
||||
response: CommunicationValue::new(CommunicationType::GetRegister)
|
||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(0)),
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
request_user_id(&client).await,
|
||||
Err(CreateUserError::InvalidResponse)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
278
type-maps.yaml
278
type-maps.yaml
|
|
@ -1,278 +0,0 @@
|
|||
protocol_version: "1.0"
|
||||
|
||||
# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended
|
||||
|
||||
# Fixed CommunicationType markers are:
|
||||
# Error: 0
|
||||
# ErrorParsing: 1
|
||||
# ErrorBadVersion: 2
|
||||
# Disconnect: 3
|
||||
# Redirect: 4
|
||||
# Shutdown: 5
|
||||
# BadRequest: 6
|
||||
# Unauthorized: 7
|
||||
# Forbidden: 8
|
||||
# NotFound: 9
|
||||
# TooManyRequests: 10
|
||||
# InternalServerError: 11
|
||||
# BadGateway: 12
|
||||
# ServiceUnavailable: 13
|
||||
# GatewayTimeout: 14
|
||||
# Identification: 15
|
||||
# IdentificationResponse: 16
|
||||
# Register: 17
|
||||
# RegisterResponse: 18
|
||||
# Ping: 19
|
||||
# Pong: 20
|
||||
|
||||
# Fixed Data Type markers are:
|
||||
# Error: 0
|
||||
# ErrorParsing: 1
|
||||
# ErrorMessage: 2
|
||||
# Version: 3
|
||||
# Description: 4
|
||||
# Timestamp: 5
|
||||
# Id: 6
|
||||
# ClientNonce: 7
|
||||
# ServerNonce: 8
|
||||
# PublicKeys: 9
|
||||
# Signature: 10
|
||||
# Connected: 11
|
||||
|
||||
type_maps:
|
||||
"1.0":
|
||||
CommunicationTypes:
|
||||
ErrorProtocol: 33
|
||||
ErrorAnonymous: 34
|
||||
ErrorInternal: 35
|
||||
ErrorInvalidData: 36
|
||||
ErrorInvalidUserId: 37
|
||||
ErrorInvalidOmikronId: 38
|
||||
ErrorNotFound: 39
|
||||
ErrorNotAuthenticated: 40
|
||||
ErrorNoIota: 41
|
||||
ErrorInvalidChallenge: 42
|
||||
ErrorInvalidSecret: 43
|
||||
ErrorInvalidPrivateKey: 44
|
||||
ErrorInvalidPublicKey: 45
|
||||
ErrorNoUserId: 46
|
||||
ErrorNoCallId: 47
|
||||
ErrorInvalidCallId: 48
|
||||
Success: 49
|
||||
ShortenLink: 50
|
||||
SettingsSave: 51
|
||||
SettingsLoad: 52
|
||||
SettingsList: 53
|
||||
GlobalSettingsSave: 54
|
||||
GlobalSettingsLoad: 55
|
||||
Message: 56
|
||||
MessageState: 57
|
||||
MessageSend: 58
|
||||
MessageLive: 59
|
||||
MessageOtherIota: 60
|
||||
MessageChunk: 61
|
||||
MessageGet: 143
|
||||
MessagesGet: 62
|
||||
PushNotification: 63
|
||||
ReadNotification: 64
|
||||
GetNotifications: 65
|
||||
TauriIdentification: 66
|
||||
ChangeConfirm: 67
|
||||
ConfirmReceive: 68
|
||||
ConfirmRead: 69
|
||||
GetChats: 70
|
||||
GetStates: 71
|
||||
AddCommunity: 72
|
||||
RemoveCommunity: 73
|
||||
GetCommunities: 74
|
||||
RegisterIota: 81
|
||||
RegisterIotaSuccess: 82
|
||||
AddConversation: 85
|
||||
SendChat: 86
|
||||
ClientChanged: 87
|
||||
ClientConnected: 88
|
||||
ClientDisconnected: 89
|
||||
ClientClosed: 90
|
||||
PublicKey: 91
|
||||
PrivateKey: 92
|
||||
WebrtcSdp: 93
|
||||
WebrtcIce: 94
|
||||
StartStream: 95
|
||||
EndStream: 96
|
||||
WatchStream: 97
|
||||
CallToken: 98
|
||||
CallInvite: 99
|
||||
CallDisconnectUser: 100
|
||||
CallTimeoutUser: 101
|
||||
CallSetAnonymousJoining: 102
|
||||
CallData: 103
|
||||
EndCall: 104
|
||||
Function: 105
|
||||
Update: 106
|
||||
CreateUser: 107
|
||||
RhoUpdate: 108
|
||||
UserConnected: 109
|
||||
UserDisconnected: 110
|
||||
IotaConnected: 111
|
||||
IotaDisconnected: 112
|
||||
SyncClientIotaStatus: 113
|
||||
GetUserData: 114
|
||||
GetIotaData: 115
|
||||
IotaUserData: 116
|
||||
ChangeUserData: 117
|
||||
ChangeIotaData: 118
|
||||
GetRegister: 119
|
||||
CompleteRegisterUser: 120
|
||||
CompleteRegisterIota: 121
|
||||
DeleteUser: 122
|
||||
DeleteIota: 123
|
||||
StartRegister: 124
|
||||
CompleteRegister: 125
|
||||
GetApp: 126
|
||||
CreateApp: 127
|
||||
DeleteApp: 128
|
||||
SaveAppData: 129
|
||||
LoadAppData: 130
|
||||
AppIdentification: 131
|
||||
AppChallenge: 132
|
||||
AppChallengeResponse: 133
|
||||
AppIdentificationResponse: 134
|
||||
LoadTxtRecord: 135
|
||||
ErrorNotSet: 136
|
||||
SetChatSecret: 139
|
||||
GetChatSecret: 140
|
||||
ChatSecretResponse: 141
|
||||
ChatSecretForward: 142
|
||||
MessageEditLive: 144
|
||||
MessageEdit: 145
|
||||
MessageReactionAdd: 146
|
||||
MessageReactionRemove: 147
|
||||
MessageReactionLive: 148
|
||||
MessageDeleteLive: 150
|
||||
ClientStateSync: 151
|
||||
ClientStateAck: 152
|
||||
StateSubscribe: 153
|
||||
DataTypes:
|
||||
ErrorType: 32
|
||||
ErrorProtocol: 33
|
||||
AcceptedIds: 34
|
||||
Uuid: 35
|
||||
RegisterId: 36
|
||||
Link: 37
|
||||
Settings: 38
|
||||
SettingsName: 39
|
||||
ChatPartnerId: 40
|
||||
ChatPartnerName: 41
|
||||
IotaId: 42
|
||||
UserId: 43
|
||||
UserIds: 44
|
||||
IotaIds: 45
|
||||
UserState: 46
|
||||
UserStates: 47
|
||||
UserPings: 48
|
||||
CallState: 49
|
||||
ScreenShare: 50
|
||||
PrivateKeyHash: 51
|
||||
# Accepted: 52 now part of default MTP
|
||||
AcceptedProfiles: 53
|
||||
DeniedProfiles: 54
|
||||
Content: 55
|
||||
Messages: 56
|
||||
Notifications: 57
|
||||
SendTime: 58
|
||||
GetTime: 59
|
||||
GetVariant: 60
|
||||
SharedSecretOwn: 61
|
||||
SharedSecretOther: 62
|
||||
SharedSecretSign: 63
|
||||
SharedSecret: 64
|
||||
CallId: 65
|
||||
CallToken: 66
|
||||
CallSecret: 67
|
||||
Untill: 68
|
||||
Enabled: 69
|
||||
StartDate: 70
|
||||
EndDate: 71
|
||||
ReceiverId: 72
|
||||
SenderId: 73
|
||||
Signed: 75
|
||||
Message: 76
|
||||
MessageState: 77
|
||||
LastPing: 78
|
||||
PingIota: 79
|
||||
PingClients: 80
|
||||
Matches: 81
|
||||
Omikron: 82
|
||||
Offset: 83
|
||||
Amount: 84
|
||||
Position: 85
|
||||
Name: 86
|
||||
Path: 87
|
||||
Codec: 88
|
||||
Function: 89
|
||||
Payload: 90
|
||||
Result: 91
|
||||
Interactables: 92
|
||||
WantToWatch: 93
|
||||
Watcher: 94
|
||||
CreatedAt: 95
|
||||
Username: 96
|
||||
Display: 97
|
||||
Avatar: 98
|
||||
About: 99
|
||||
Status: 100
|
||||
PublicKey: 101
|
||||
SubLevel: 102
|
||||
SubEnd: 103
|
||||
CommunityAddress: 104
|
||||
CommunityTitle: 106
|
||||
Communities: 107
|
||||
RhoConnections: 108
|
||||
User: 109
|
||||
OnlineStatus: 110
|
||||
OmikronId: 111
|
||||
OmikronConnections: 112
|
||||
ResetToken: 113
|
||||
NewToken: 114
|
||||
CallInvited: 115
|
||||
CallMembers: 116
|
||||
Calls: 117
|
||||
Timeout: 118
|
||||
HasAdmin: 119
|
||||
LastMessageAt: 120
|
||||
Height: 121
|
||||
SentBySelf: 122
|
||||
SessionId: 123
|
||||
Contacts: 124
|
||||
LastMessage: 125
|
||||
AppIdentifier: 127
|
||||
AppPrivateKey: 128
|
||||
AppPublicKey: 129
|
||||
AppSession: 130
|
||||
AppData: 131
|
||||
TauriToken: 132
|
||||
Challenge: 133
|
||||
EncryptedPayload: 134
|
||||
SecurePayload: 135
|
||||
DeviceId: 136
|
||||
ClientId: 137
|
||||
SecretId: 142
|
||||
VersionNumber: 143
|
||||
EncryptedSecret: 144
|
||||
WrappingScheme: 146
|
||||
UpdatedAt: 147
|
||||
ChatId: 148
|
||||
KemCiphertext: 149
|
||||
SenderUserId: 152
|
||||
RecipientUserId: 153
|
||||
Recipients: 154
|
||||
Edited: 155
|
||||
Reactions: 156
|
||||
Reaction: 157
|
||||
ReplyId: 158
|
||||
CacheValid: 159
|
||||
CacheSchemaVersion: 160
|
||||
SyncMode: 161
|
||||
MessageId: 162
|
||||
DeletedMessageIds: 163
|
||||
DeletedContactIds: 164
|
||||
Loading…
Reference in a new issue