use turkos_userspace::service_registry::{Error, Grant, Name, Namespace, PublisherId, Registry, Visibility, VisibilityGrant, WireError, WIRE_RESPONSE_BYTES}; fn name(bytes: &[u8]) -> Name { Name::parse(bytes).unwrap() } fn pid(slot: u32, generation: u64) -> PublisherId { PublisherId::new(slot, generation).unwrap() } #[test] fn os_service_registry_names_positive() { let console = name(b"console"); let file = name(b"file"); let a = pid(1, 7); let b = pid(2, 9); let mut registry = Registry::with_grants([ Grant::new(console, a), Grant::new(file, b)]).unwrap(); assert_eq!(registry.count(), 0); let first = registry.publish(a, console, 0x71).unwrap(); let second = registry.publish(b, file, 0x92).unwrap(); assert_eq!(registry.count(), 2); assert_eq!(registry.lookup(console), Ok(first)); assert_eq!(registry.lookup(file), Ok(second)); assert_eq!(first.publisher, a); assert_eq!(second.publisher, b); assert_eq!(registry.unpublish(a, console), Ok(first)); assert_eq!(registry.lookup(console), Err(Error::NotFound)); assert_eq!(registry.lookup(file), Ok(second)); assert_eq!(registry.count(), 1); } #[test] fn os_service_registry_names_negative() { let console = name(b"console"); let file = name(b"file"); let a = pid(1, 7); let foreign = pid(2, 11); let stale_generation = pid(1, 6); let mut registry = Registry::with_grants([ Grant::new(console, a), Grant::new(file, foreign)]).unwrap(); let empty = registry.snapshot(); assert_eq!(registry.publish(foreign, console, 0x99), Err(Error::Access)); assert_eq!(registry.snapshot(), empty); let original = registry.publish(a, console, 0x71).unwrap(); let before = registry.snapshot(); let grants = registry.grants(); assert_eq!(registry.publish(foreign, console, 0x99), Err(Error::Access)); assert_eq!(registry.unpublish(foreign, console), Err(Error::Access)); assert_eq!(registry.unpublish(stale_generation, console), Err(Error::Access)); assert_eq!(registry.snapshot(), before); assert_eq!(registry.grants(), grants); assert_eq!(registry.count(), 1); assert_eq!(registry.lookup(console), Ok(original)); assert_eq!(registry.publish(foreign, file, 0x92).unwrap().endpoint, 0x92); assert_eq!(registry.lookup(console), Ok(original)); assert_eq!(registry.unpublish(a, console), Ok(original)); } #[test] fn invalid_names_policy_and_generation_do_not_publish_partial_state() { assert_eq!(Name::parse(b""), Err(Error::InvalidName)); assert_eq!(Name::parse(b"Console"), Err(Error::InvalidName)); assert_eq!(Name::parse(b"bad/name"), Err(Error::InvalidName)); assert_eq!(PublisherId::new(0, 1), Err(Error::Access)); assert_eq!(PublisherId::new(1, 0), Err(Error::Access)); let a = pid(1, 7); let console = name(b"console"); assert!(matches!(Registry::<2>::with_grants([ Grant::new(console, a), Grant::new(console, pid(2, 9))]), Err(Error::Occupied))); let mut registry = Registry::with_grants([Grant::new(console, a)]).unwrap(); let first = registry.publish(a, name(b"console"), 0x71).unwrap(); let before = registry.snapshot(); assert_eq!(registry.publish(a, name(b"file"), 0x72), Err(Error::Access)); assert_eq!(registry.publish(a, console, 0), Err(Error::InvalidEndpoint)); assert_eq!(registry.publish(a, console, 0x72), Err(Error::Occupied)); assert_eq!(registry.snapshot(), before); assert_eq!(registry.lookup(name(b"console")), Ok(first)); } fn wire(kind: u8, version: u8, name: &[u8], endpoint: Option) -> Vec { let length = 1 + name.len() + if endpoint.is_some() { 8 } else { 0 }; let mut bytes = vec![b'S', b'R', version, kind, length as u8, 0, 0, 0, name.len() as u8]; bytes.extend_from_slice(name); if let Some(value) = endpoint { bytes.extend_from_slice(&value.to_le_bytes()); } bytes } #[test] fn os_service_registry_versions_positive() { let a = pid(1, 7); let file = name(b"file"); let mut registry = Registry::with_grants([Grant::new(file, a)]).unwrap(); let mut response = [0xa5; WIRE_RESPONSE_BYTES]; let published = wire(1, 1, b"file", Some(0x92)); assert_eq!(registry.handle_v1(a, &published, &mut response), Ok(WIRE_RESPONSE_BYTES)); assert_eq!(response, [b'S', b'R', 1, 0x81, 9, 0, 0, 0, 0, 0x92, 0, 0, 0, 0, 0, 0, 0]); assert_eq!(registry.count(), 1); assert_eq!(registry.lookup(file).unwrap().publisher, a); assert_eq!(registry.lookup(file).unwrap().endpoint, 0x92); let removed = wire(2, 1, b"file", None); assert_eq!(registry.handle_v1(a, &removed, &mut response), Ok(WIRE_RESPONSE_BYTES)); assert_eq!(response[3], 0x82); assert_eq!(&response[9..17], &0x92u64.to_le_bytes()); assert_eq!(registry.count(), 0); assert_eq!(registry.lookup(file), Err(Error::NotFound)); } #[test] fn os_service_registry_versions_negative() { let a = pid(1, 7); let foreign = pid(2, 9); let file = name(b"file"); let mut registry = Registry::with_grants([Grant::new(file, a)]).unwrap(); let before = registry.snapshot(); let grants = registry.grants(); let mut response = [0xa5; WIRE_RESPONSE_BYTES]; let baseline = response; let v255 = wire(1, 255, b"file", Some(0x92)); assert_eq!(registry.handle_v1(a, &v255, &mut response), Err(WireError::Version)); for count in 0..8 { assert_eq!(registry.handle_v1(a, &v255[..count], &mut response), Err(WireError::Truncated)); } let valid = wire(1, 1, b"file", Some(0x92)); assert_eq!(registry.handle_v1(a, &valid[..valid.len()-1], &mut response), Err(WireError::Length)); let mut oversized = valid.clone(); oversized[4] = 34; assert_eq!(registry.handle_v1(a, &oversized, &mut response), Err(WireError::Length)); let mut flagged = valid.clone(); flagged[6] = 1; assert_eq!(registry.handle_v1(a, &flagged, &mut response), Err(WireError::Header)); let mut wrong_kind = valid.clone(); wrong_kind[3] = 0x81; assert_eq!(registry.handle_v1(a, &wrong_kind, &mut response), Err(WireError::Kind)); assert_eq!(registry.handle_v1(a, &valid, &mut response[..16]), Err(WireError::ResponseSpace)); assert_eq!(registry.handle_v1(foreign, &valid, &mut response), Err(WireError::Registry(Error::Access))); assert_eq!(registry.snapshot(), before); assert_eq!(registry.grants(), grants); assert_eq!(registry.count(), 0); assert_eq!(response, baseline); assert_eq!(registry.handle_v1(a, &valid, &mut response), Ok(WIRE_RESPONSE_BYTES)); assert_eq!(registry.count(), 1); assert_eq!(registry.lookup(file).unwrap().endpoint, 0x92); } #[test] fn os_service_registry_restart_positive() { let name = name(b"file"); let old = pid(1, 7); let fresh = pid(1, 8); let mut registry = Registry::with_grants([Grant::new(name, old)]).unwrap(); let original = registry.publish(old, name, 0x92).unwrap(); let stale = registry.open_session(name).unwrap(); assert_eq!(registry.validate_session(stale), Ok(original)); assert_eq!(registry.count(), 1); assert_eq!(registry.rebind_stopped(name, old, fresh), Ok(Some(original))); assert_eq!(registry.count(), 0); assert_eq!(registry.lookup(name), Err(Error::NotFound)); assert_eq!(registry.grants(), [Grant::new(name, fresh)]); assert_eq!(registry.validate_session(stale), Err(Error::StaleSession)); let replacement = registry.publish(fresh, name, 0x93).unwrap(); let new_session = registry.open_session(name).unwrap(); assert_eq!(new_session.publisher(), fresh); assert_eq!(new_session.endpoint(), 0x93); assert_eq!(registry.validate_session(new_session), Ok(replacement)); assert_eq!(registry.validate_session(stale), Err(Error::StaleSession)); assert_eq!(registry.count(), 1); } #[test] fn os_service_registry_restart_negative() { let name = name(b"file"); let old = pid(1, 7); let fresh = pid(1, 8); let foreign = pid(2, 9); let mut registry = Registry::with_grants([Grant::new(name, old)]).unwrap(); let original = registry.publish(old, name, 0x92).unwrap(); let replay = registry.open_session(name).unwrap(); let before = registry.snapshot(); let grants = registry.grants(); assert_eq!(registry.rebind_stopped(name, foreign, fresh), Err(Error::Access)); assert_eq!(registry.rebind_stopped(name, old, old), Err(Error::Access)); assert_eq!(registry.rebind_stopped(Name::parse(b"missing").unwrap(), old, fresh), Err(Error::NotFound)); assert_eq!(registry.snapshot(), before); assert_eq!(registry.grants(), grants); assert_eq!(registry.count(), 1); assert_eq!(registry.validate_session(replay), Ok(original)); assert_eq!(registry.rebind_stopped(name, old, fresh), Ok(Some(original))); // Reusing the endpoint number cannot revive a session from an old PID. let replacement = registry.publish(fresh, name, 0x92).unwrap(); let after = registry.snapshot(); let grants = registry.grants(); assert_eq!(registry.validate_session(replay), Err(Error::StaleSession)); assert_eq!(registry.snapshot(), after); assert_eq!(registry.grants(), grants); assert_eq!(registry.count(), 1); assert_eq!(registry.open_session(name).and_then(|session| registry.validate_session(session)), Ok(replacement)); } #[test] fn os_service_registry_discovery_positive() { let console = name(b"console"); let file = name(b"file"); let admin = name(b"admin"); let service = pid(1, 7); let client = pid(2, 8); let manager = pid(3, 9); let mut registry = Registry::with_grants([ Grant::new(console, service), Grant::new(file, service), Grant::new(admin, service)]).unwrap(); let console_binding = registry.publish(service, console, 0x71).unwrap(); let file_binding = registry.publish(service, file, 0x92).unwrap(); registry.publish(service, admin, 0xa3).unwrap(); let visibility = Visibility::with_grants([ VisibilityGrant::new(client, Namespace::Public, console), VisibilityGrant::new(client, Namespace::Public, file), VisibilityGrant::new(manager, Namespace::Management, admin), ]).unwrap(); let before = registry.snapshot(); let policy = visibility; assert_eq!(visibility.query(®istry, client, Namespace::Public, console), Ok(console_binding)); assert_eq!(visibility.query(®istry, client, Namespace::Public, file), Ok(file_binding)); assert_eq!(registry.snapshot(), before); assert_eq!(registry.count(), 3); assert_eq!(visibility, policy); } #[test] fn os_service_registry_discovery_negative() { let console = name(b"console"); let file = name(b"file"); let admin = name(b"admin"); let missing = name(b"missing"); let service = pid(1, 7); let client = pid(2, 8); let manager = pid(3, 9); let mut registry = Registry::with_grants([ Grant::new(console, service), Grant::new(file, service), Grant::new(admin, service)]).unwrap(); registry.publish(service, console, 0x71).unwrap(); registry.publish(service, file, 0x92).unwrap(); registry.publish(service, admin, 0xa3).unwrap(); let visibility = Visibility::with_grants([ VisibilityGrant::new(client, Namespace::Public, console), VisibilityGrant::new(client, Namespace::Public, file), VisibilityGrant::new(manager, Namespace::Management, admin), ]).unwrap(); let before = registry.snapshot(); let grants = registry.grants(); let policy = visibility; let hidden = visibility.query(®istry, client, Namespace::Management, admin); let unknown = visibility.query(®istry, client, Namespace::Management, missing); assert_eq!(hidden, Err(Error::NotFound)); assert_eq!(unknown, Err(Error::NotFound)); assert_eq!(hidden, unknown); assert_eq!(visibility.query(®istry, manager, Namespace::Public, console), Err(Error::NotFound)); assert_eq!(visibility.query(®istry, client, Namespace::Public, admin), Err(Error::NotFound)); assert_eq!(registry.snapshot(), before); assert_eq!(registry.grants(), grants); assert_eq!(registry.count(), 3); assert_eq!(visibility, policy); assert!(matches!(Visibility::with_grants([ VisibilityGrant::new(client, Namespace::Public, console), VisibilityGrant::new(client, Namespace::Public, console)]), Err(Error::Occupied))); assert_eq!(visibility.query(®istry, client, Namespace::Public, file) .unwrap().endpoint, 0x92); }