use serde::{Deserialize, Serialize};
pub const MIN_SUPPORTED_PROTOCOL_VERSION: u32 = 1;
pub const PROTOCOL_VERSION: u32 = 4;
pub const DEVTOOLS_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PeerKind {
Target,
Devtools,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Hello {
pub protocol_version: u32,
pub devtools_version: String,
pub kind: PeerKind,
pub auth_token: String,
#[serde(default)]
pub take_over: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorCode {
ProtocolVersionMismatch,
Unauthorized,
UnknownId,
StaleId,
Unsupported,
InvalidRequest,
InternalError,
SessionBusy,
}
macro_rules! opaque_id {
($name:ident) => {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct $name {
index: u64,
generation: u64,
}
impl $name {
pub const fn new(index: u64, generation: u64) -> Self {
Self { index, generation }
}
pub const fn index(&self) -> u64 {
self.index
}
pub const fn generation(&self) -> u64 {
self.generation
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}:{}",
stringify!($name),
self.index,
self.generation
)
}
}
};
}
opaque_id!(DevWindowId);
opaque_id!(DevWidgetId);
opaque_id!(DevRenderId);
opaque_id!(DevLayerId);
opaque_id!(DevSemanticsId);
opaque_id!(DevSignalId);
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DebugValue {
Bool(bool),
Int(i64),
Uint(u64),
Float(f64),
Str(String),
Color(u8, u8, u8, u8),
Enum(String),
Size([f32; 2]),
Offset([f32; 2]),
Rect([f32; 4]),
Insets([f32; 4]),
Constraints {
min_width: f32,
#[serde(with = "unbounded_maximum")]
max_width: f32,
min_height: f32,
#[serde(with = "unbounded_maximum")]
max_height: f32,
},
Optional(Option<Box<DebugValue>>),
List(Vec<DebugValue>),
Redacted,
}
mod animation_speed_payload {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Serialize, Deserialize)]
struct Payload {
scale: f32,
}
pub(super) fn serialize<S: Serializer>(scale: &f32, serializer: S) -> Result<S::Ok, S::Error> {
Payload { scale: *scale }.serialize(serializer)
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<f32, D::Error> {
Payload::deserialize(deserializer).map(|payload| payload.scale)
}
}
mod profiler_mode_payload {
use super::DevtoolsProfilerMode;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Serialize, Deserialize)]
struct Payload {
mode: DevtoolsProfilerMode,
}
pub(super) fn serialize<S: Serializer>(
mode: &DevtoolsProfilerMode,
serializer: S,
) -> Result<S::Ok, S::Error> {
Payload { mode: *mode }.serialize(serializer)
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<DevtoolsProfilerMode, D::Error> {
Payload::deserialize(deserializer).map(|payload| payload.mode)
}
}
mod unbounded_maximum {
use serde::{Deserialize, Deserializer, Serializer, ser::Error};
pub fn serialize<S: Serializer>(value: &f32, serializer: S) -> Result<S::Ok, S::Error> {
if value.is_finite() {
serializer.serialize_f32(*value)
} else if *value == f32::INFINITY {
serializer.serialize_none()
} else {
Err(S::Error::custom(
"constraint maximum must be finite or positive infinity",
))
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<f32, D::Error> {
Option::<f32>::deserialize(deserializer).map(|maximum| maximum.unwrap_or(f32::INFINITY))
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DebugProperty {
pub name: String,
pub value: DebugValue,
#[serde(default)]
pub overridden: bool,
#[serde(default)]
pub editable: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub options: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step: Option<f64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PropertyChange {
pub name: String,
pub old: Option<DebugValue>,
pub new: Option<DebugValue>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum InvalidationReason {
SignalWrite {
signal: DevSignalId,
name: String,
old: Option<String>,
new: Option<String>,
},
ParentReconciliation {
changed: Vec<PropertyChange>,
},
WidgetConfigurationChanged,
EnvironmentChanged {
field: String,
old: Option<String>,
new: Option<String>,
},
LocaleChanged,
WindowMetricsChanged,
ConstraintsChanged,
Animation,
TaskCompletion,
Navigation,
Restoration,
ManualInvalidation,
Mounted,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
pub column: u32,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct WidgetNode {
pub id: DevWidgetId,
pub parent: Option<DevWidgetId>,
pub type_name: String,
pub key: Option<String>,
pub label: Option<String>,
pub child_ids: Vec<DevWidgetId>,
pub revision: u64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum TreeDelta {
Snapshot {
window: DevWindowId,
root: Box<WidgetNode>,
nodes: Vec<WidgetNode>,
truncated: bool,
},
Insert {
node: Box<WidgetNode>,
},
Remove {
id: DevWidgetId,
},
Move {
id: DevWidgetId,
parent: DevWidgetId,
position: usize,
},
Update {
node: Box<WidgetNode>,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ElementState {
pub mount_generation: u64,
pub builds: u64,
pub layouts: u64,
pub paints: u64,
pub composites: u64,
pub semantic_updates: u64,
pub constraints: Option<DebugValue>,
pub size: Option<[f32; 2]>,
pub offset: Option<[f32; 2]>,
pub world_bounds: Option<[f32; 4]>,
#[serde(default)]
pub baseline: Option<f32>,
#[serde(default)]
pub clip: Option<[f32; 4]>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LayoutInspection {
pub node: DevWidgetId,
pub parent: Option<DevWidgetId>,
pub incoming_constraints: Option<DebugValue>,
pub resolved_size: [f32; 2],
pub local_offset: [f32; 2],
pub local_transform: [f32; 6],
pub world_transform: [f32; 6],
pub world_bounds: [f32; 4],
pub content_bounds: [f32; 4],
pub padding: Option<[f32; 4]>,
pub clip: Option<[f32; 4]>,
pub baseline: Option<f32>,
pub details: LayoutDetails,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LayoutHistoryEntry {
pub sequence: u64,
pub old_constraints: Option<DebugValue>,
pub new_constraints: DebugValue,
pub old_size: [f32; 2],
pub new_size: [f32; 2],
pub cause: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkReasons {
pub layout: Option<String>,
pub paint: Option<String>,
pub composite: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum LayoutDetails {
Box,
Flex {
axis: String,
available_main: Option<f32>,
non_flex_extent: f32,
flexible_extent: f32,
used_extent: f32,
remaining_extent: f32,
overflow: f32,
children: Vec<FlexChildInspection>,
},
Stack {
alignment: [f32; 2],
indexed_active: Option<usize>,
children: Vec<StackChildInspection>,
},
Positioned {
left: Option<f32>,
right: Option<f32>,
top: Option<f32>,
bottom: Option<f32>,
width: Option<f32>,
height: Option<f32>,
},
Transform {
matrix: [f32; 6],
determinant: f32,
invertible: bool,
},
Fitted {
fit: String,
alignment: [f32; 2],
source_size: Option<[f32; 2]>,
destination_size: [f32; 2],
matrix: [f32; 6],
determinant: f32,
invertible: bool,
},
Scroll {
axis: String,
viewport_extent: f32,
content_extent: f32,
offset: f32,
min_scroll: f32,
max_scroll: f32,
},
LazyViewport {
item_count: usize,
materialized_start: usize,
materialized_end: usize,
materialized_items: usize,
viewport_extent: f32,
cache_extent: f32,
scroll_offset: f32,
},
Text {
text_length: usize,
max_lines: Option<usize>,
overflow: String,
line_count: Option<usize>,
},
Custom {
layout_kind: String,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FlexChildInspection {
pub node: DevWidgetId,
pub index: usize,
pub type_name: String,
pub size: [f32; 2],
pub offset: [f32; 2],
pub flex: Option<u32>,
pub fit: Option<String>,
pub allocated_main_extent: Option<f32>,
pub actual_main_extent: f32,
pub cross_extent: f32,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StackChildInspection {
pub node: DevWidgetId,
pub index: usize,
pub type_name: String,
pub bounds: [f32; 4],
pub painted: bool,
pub left: Option<f32>,
pub right: Option<f32>,
pub top: Option<f32>,
pub bottom: Option<f32>,
pub width: Option<f32>,
pub height: Option<f32>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct NodeDetails {
pub id: DevWidgetId,
pub window: DevWindowId,
pub type_name: String,
pub key: Option<String>,
pub state: ElementState,
#[serde(default)]
pub layout: Option<LayoutInspection>,
#[serde(default)]
pub layout_history: Vec<LayoutHistoryEntry>,
#[serde(default)]
pub work_reasons: WorkReasons,
pub properties: Vec<DebugProperty>,
#[serde(default)]
pub property_changes: Vec<PropertyChange>,
#[serde(default)]
pub invalidation_causes: Vec<InvalidationReason>,
#[serde(default)]
pub consumed_signals: Vec<DevSignalId>,
pub invalidation: Option<InvalidationReason>,
pub render: Option<DevRenderId>,
pub semantics: Option<DevSemanticsId>,
pub source: Option<SourceLocation>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct FrameTimingsWire {
pub event_processing: u32,
pub runtime_messages: u32,
pub build: u32,
pub layout: u32,
pub composite: u32,
pub semantics: u32,
pub paint: u32,
pub cpu_total: u32,
pub prepare: u32,
pub encode: u32,
pub submit: u32,
pub gpu_us: Option<f64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FrameRecordEvent {
pub window: DevWindowId,
pub frame: u64,
pub timings: FrameTimingsWire,
#[serde(default)]
pub budget_us: Option<u32>,
pub over_budget: bool,
pub draw_calls: u32,
pub instances: u32,
pub upload_bytes: u64,
pub pipelines_created: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DevtoolsProfilerMode {
#[default]
Basic,
Performance,
Deep,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TracePhase {
Build,
Layout,
Paint,
Semantics,
Composite,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TraceEvent {
pub node: DevWidgetId,
pub phase: TracePhase,
pub parent: Option<u32>,
pub start_us: u32,
pub duration_us: u32,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DeepFrameTrace {
pub window: DevWindowId,
pub frame: u64,
pub events: Vec<TraceEvent>,
pub truncated: bool,
pub dropped_events: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ResourceCounts {
pub elements: usize,
pub render_objects: usize,
pub layers: usize,
pub semantics_nodes: usize,
pub signals: usize,
pub tasks_active: usize,
pub glyph_atlas_pages: usize,
pub image_resources: usize,
pub gradient_resources: usize,
pub path_meshes: usize,
pub offscreen_bytes: usize,
pub effect_cached_bytes: usize,
pub rss_mb: usize,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MemorySnapshot {
pub label: String,
pub counts: ResourceCounts,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SignalSummary {
pub id: DevSignalId,
pub name: Option<String>,
pub type_name: String,
#[serde(default)]
pub generation: u64,
pub write_count: u64,
pub subscriber_count: usize,
pub last_write_summary: Option<String>,
pub editable: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SignalSubscriber {
pub signal: DevSignalId,
pub element: DevWidgetId,
pub path: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DebugOption {
LayoutBounds,
LayoutBoundsSubtree,
LayoutBoundsWholeWindow,
PaddingContent,
Baselines,
Clips,
HitTestRegions,
SemanticsBounds,
ScrollViewports,
LayerBoundaries,
RepaintRainbow,
FlashRebuilds,
HighlightBuild,
HighlightLayout,
HighlightPaint,
HighlightSemantics,
HighlightComposite,
HighlightOversizedImages,
PerformanceOverlay,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CacheKind {
TextLayouts,
GlyphAtlas,
Images,
Paths,
Gradients,
Offscreen,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EditableValue {
Bool(bool),
Int(i64),
Uint(u64),
Float(f64),
Str(String),
Color(u8, u8, u8, u8),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct CacheReport {
pub which: CacheKind,
pub entries: usize,
pub hits: u64,
pub misses: u64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "method")]
pub enum RequestMethod {
GetTargetInfo,
GetWidgetTree {
window: DevWindowId,
},
GetNodeDetails {
id: DevWidgetId,
},
EditProperty {
id: DevWidgetId,
name: String,
value: DebugValue,
},
StartInspectMode {
window: DevWindowId,
},
StopInspectMode {
window: DevWindowId,
},
HighlightNode {
window: DevWindowId,
id: Option<DevWidgetId>,
},
SetDebugOption {
name: DebugOption,
enabled: bool,
},
SetProfilerMode {
mode: DevtoolsProfilerMode,
},
StartRecording,
StopRecording,
TakeMemorySnapshot {
label: String,
},
ListSignals,
GetSignalSubscribers {
id: DevSignalId,
},
EditSignal {
id: DevSignalId,
value: EditableValue,
},
ResetOverrides,
ClearCache {
which: CacheKind,
},
SetAnimationSpeed {
scale: f32,
},
GetRuntimeDiagnostics {
window: DevWindowId,
},
GetSemanticsTree {
window: DevWindowId,
},
GetLayerTree {
window: DevWindowId,
},
GetWorkCounts {
window: DevWindowId,
limit: u32,
},
HighlightRect {
window: DevWindowId,
rect: Option<[f32; 4]>,
},
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct WidgetPipelineCounters {
pub mounts: u64,
pub unmounts: u64,
pub rebuilds: u64,
pub layouts: u64,
pub paints: u64,
pub composites: u64,
pub animation_ticks: u64,
pub scroll_offset_updates: u64,
pub reconciliation_fast_paths: u64,
pub layout_cache_hits: u64,
pub display_lists_reused: u64,
pub compositor_only_updates: u64,
pub lazy_layouts: u64,
pub items_built: u64,
pub items_reused: u64,
pub child_list_scans: u64,
pub identical_child_bailouts: u64,
pub elements_created: u64,
pub elements_removed: u64,
pub elements_moved: u64,
pub dirty_requests: u64,
pub dirty_queue_deduplicated: u64,
pub elements_total: u64,
pub render_objects_total: u64,
pub layers_total: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct TextCacheCounters {
pub layouts_requested: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub font_resolution_requests: u64,
pub font_resolution_cache_hits: u64,
pub font_resolution_cache_misses: u64,
pub coverage_queries: u64,
pub fallback_runs: u64,
pub fallback_fonts_used: u64,
pub missing_clusters: u64,
pub shaping_runs: u64,
pub paragraphs_considered: u64,
pub paragraphs_reshaped: u64,
pub parley_layouts_reused: u64,
pub documents_composed: u64,
pub documents_reused: u64,
pub width_reuses: u64,
pub cache_growths: u64,
pub cache_shrinks: u64,
pub paragraphs_carried: u64,
pub shapes_reused: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct SemanticsCounters {
pub nodes: u64,
pub nodes_created: u64,
pub nodes_removed: u64,
pub nodes_updated: u64,
pub property_updates: u64,
pub geometry_updates: u64,
pub native_updates: u64,
pub actions_received: u64,
pub revisions: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct CompositorCounters {
pub layers: u64,
pub picture_layers_repainted: u64,
pub transform_updates: u64,
pub clip_updates: u64,
pub layers_culled: u64,
pub opacity_updates: u64,
pub leader_publish_passes: u64,
pub leaders_published: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct RuntimeDiagnosticsWire {
pub widgets: WidgetPipelineCounters,
pub text: TextCacheCounters,
pub semantics: SemanticsCounters,
pub compositor: CompositorCounters,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct SemanticsNodeWire {
pub id: u64,
pub parent: Option<u64>,
pub children: Vec<u64>,
pub role: String,
pub label: Option<String>,
pub value: Option<String>,
pub description: Option<String>,
pub bounds: [f32; 4],
pub flags: Vec<String>,
pub actions: Vec<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct LayerNodeWire {
pub id: u64,
pub parent: Option<u64>,
pub children: Vec<u64>,
pub kind: String,
pub summary: String,
pub bounds: Option<[f32; 4]>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkCountEntry {
pub id: DevWidgetId,
pub type_name: String,
#[serde(default)]
pub builds: u64,
#[serde(default)]
pub layouts: u64,
#[serde(default)]
pub paints: u64,
#[serde(default)]
pub semantics: u64,
#[serde(default)]
pub composites: u64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "result")]
pub enum ResponsePayload {
Ok,
TargetInfo(Box<TargetInfo>),
WidgetTree {
deltas: Vec<TreeDelta>,
tree_revision: u64,
},
NodeDetails(Box<NodeDetails>),
RecordingStarted,
RecordingStopped {
frames: u64,
},
MemorySnapshot(MemorySnapshot),
Signals {
signals: Vec<SignalSummary>,
},
SignalSubscribers {
subscribers: Vec<SignalSubscriber>,
},
Edited,
OverridesReset,
CacheCleared(CacheReport),
AnimationSpeedSet(#[serde(with = "animation_speed_payload")] f32),
ProfilerModeSet(#[serde(with = "profiler_mode_payload")] DevtoolsProfilerMode),
RuntimeDiagnostics(Box<RuntimeDiagnosticsWire>),
SemanticsTree {
window: DevWindowId,
nodes: Vec<SemanticsNodeWire>,
truncated: bool,
},
LayerTree {
window: DevWindowId,
layers: Vec<LayerNodeWire>,
truncated: bool,
},
WorkCounts {
window: DevWindowId,
entries: Vec<WorkCountEntry>,
},
Error {
code: ErrorCode,
message: String,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "event")]
pub enum TargetEvent {
WidgetTreeDeltas {
window: DevWindowId,
deltas: Vec<TreeDelta>,
tree_revision: u64,
},
FrameRecord(FrameRecordEvent),
DeepTrace(DeepFrameTrace),
WidgetSelectedByUser {
window: DevWindowId,
id: DevWidgetId,
},
Log {
level: String,
target: String,
message: String,
},
WindowsChanged,
DroppedTelemetry {
count: u64,
},
InspectModeEnded {
window: DevWindowId,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum Message {
Request {
request_id: u64,
body: RequestMethod,
},
Response {
request_id: u64,
payload: Result<ResponsePayload, ErrorCode>,
},
Event(TargetEvent),
Rejection {
code: ErrorCode,
message: String,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TargetInfo {
pub protocol_version: u32,
pub framework_version: String,
pub pid: u32,
pub executable: String,
pub platform: String,
pub windows: Vec<WindowSummary>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct WindowSummary {
pub id: DevWindowId,
pub title: String,
pub logical_size: [f32; 2],
pub scale_factor: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DiscoveryRecord {
pub pid: u32,
pub app_name: String,
pub port: u16,
pub auth_token: String,
pub started_unix_ms: u64,
pub protocol_version: u32,
}
pub fn check_hello(
hello: &Hello,
expected_kind: PeerKind,
expected_token: Option<&str>,
) -> Result<(), ErrorCode> {
if !(MIN_SUPPORTED_PROTOCOL_VERSION..=PROTOCOL_VERSION).contains(&hello.protocol_version) {
return Err(ErrorCode::ProtocolVersionMismatch);
}
if let Some(token) = expected_token {
if !token_eq(&hello.auth_token, token) {
return Err(ErrorCode::Unauthorized);
}
}
if hello.kind != expected_kind {
return Err(ErrorCode::InvalidRequest);
}
Ok(())
}
fn token_eq(a: &str, b: &str) -> bool {
let (a, b) = (a.as_bytes(), b.as_bytes());
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
diff |= x ^ y;
}
diff == 0
}