Merge branch 'rpc-objectmap' into 'main'

RPC: revise semantics for weak references and object IDs

Closes #848

See merge request tpo/core/arti!1183
This commit is contained in:
Nick Mathewson 2023-05-16 13:30:42 +00:00
commit ef3c049064
5 changed files with 211 additions and 228 deletions

4
Cargo.lock generated
View File

@ -232,15 +232,19 @@ version = "0.1.0"
dependencies = [
"arti-client",
"asynchronous-codec",
"base64ct",
"bytes",
"erased-serde",
"futures",
"futures-await-test",
"pin-project",
"rand 0.8.5",
"serde",
"serde_json",
"thiserror",
"tor-async-utils",
"tor-basic-utils",
"tor-bytes",
"tor-error",
"tor-rpcbase",
"tor-rtcompat",

View File

@ -18,6 +18,7 @@ full = ["arti-client/full", "tor-async-utils/full", "tor-error/full", "tor-rpcba
[dependencies]
arti-client = { path = "../arti-client", version = "0.9.0", features = ["rpc"] }
asynchronous-codec = { version = "0.6.0", features = ["json"] }
base64ct = "1.5.1"
bytes = "1"
erased-serde = "0.3.25"
futures = "0.3.14"
@ -25,10 +26,12 @@ futures = "0.3.14"
# out.
# generational-arena = "0.2.8"
pin-project = "1"
rand = "0.8"
serde = { version = "1.0.103", features = ["derive"] }
serde_json = "1.0.50"
thiserror = "1"
tor-async-utils = { path = "../tor-async-utils", version = "0.1.0" }
tor-bytes = { path = "../tor-bytes", version = "0.7.0" }
tor-error = { path = "../tor-error", version = "0.5.0" }
tor-rpcbase = { path = "../tor-rpcbase", version = "0.1.0" }
tor-rtcompat = { path = "../tor-rtcompat", version = "0.9.0" }
@ -37,3 +40,4 @@ typetag = "0.2.7"
[dev-dependencies]
futures-await-test = "0.3.0"
tor-basic-utils = { path = "../tor-basic-utils", version = "0.7.0" }

View File

@ -103,7 +103,7 @@ impl Connection {
} else {
inner
.objects
.lookup(id.try_into()?)
.lookup(crate::objmap::GenIdx::try_decode(id)?)
.ok_or(rpc::LookupError::NoObject(id.clone()))
}
}
@ -364,7 +364,7 @@ impl rpc::Context for RequestContext {
.expect("Lock poisoned")
.objects
.insert_strong(object)
.into()
.encode()
}
fn register_weak(&self, object: Arc<dyn rpc::Object>) -> rpc::ObjectId {
@ -374,7 +374,7 @@ impl rpc::Context for RequestContext {
.expect("Lock poisoned")
.objects
.insert_weak(object)
.into()
.encode()
}
}

View File

@ -93,30 +93,34 @@ mod fake_generational_arena {
/// A mechanism to look up RPC `Objects` by their `ObjectId`.
#[derive(Default)]
pub(crate) struct ObjMap {
/// Generationally indexed arena of object references.
/// Generationally indexed arena of strong object references.
strong_arena: Arena<Arc<dyn rpc::Object>>,
/// Generationally indexed arena of weak object references.
///
/// Invariants:
/// * No object has more than one reference in this arena.
/// * Every `entry` in this arena at position `idx` has a corresponding
/// entry in `reverse_map` entry such that
/// `reverse_map[entry.tagged_addr()] == idx`.
arena: Arena<ArenaEntry>,
/// Backwards reference to look up arena references by the underlying object identity.
weak_arena: Arena<WeakArenaEntry>,
/// Backwards reference to look up weak arena references by the underlying
/// object identity.
///
/// Invariants:
/// * For every `(addr,idx)` entry in this map, there is a corresponding
/// ArenaEntry in `arena` such that `arena[idx].tagged_addr() == addr`
reverse_map: HashMap<TaggedAddr, GenIdx>,
/// * For every weak `(addr,idx)` entry in this map, there is a
/// corresponding ArenaEntry in `arena` such that
/// `arena[idx].tagged_addr() == addr`
reverse_map: HashMap<TaggedAddr, generational_arena::Index>,
/// Testing only: How many times have we tidied this map?
#[cfg(test)]
n_tidies: usize,
}
/// A single entry to an Object stored in the generational arena.
/// A single entry to a weak Object stored in the generational arena.
///
struct ArenaEntry {
struct WeakArenaEntry {
/// The actual Arc or Weak reference for the object that we're storing here.
obj: ObjRef,
obj: Weak<dyn rpc::Object>,
///
/// This contains a strong or weak reference, along with the object's true TypeId.
/// See the [`TaggedAddr`] for more info on
@ -124,35 +128,6 @@ struct ArenaEntry {
id: any::TypeId,
}
/// Strong or weak reference to an Object.
enum ObjRef {
/// A strong reference
Strong(Arc<dyn rpc::Object>),
/// A weak reference
Weak(Weak<dyn rpc::Object>),
}
impl ObjRef {
/// Try to return a strong reference to this object, upgrading a weak
/// reference if needed.
///
/// A `None` return indicates a dangling weak reference.
fn strong(&self) -> Option<Arc<dyn rpc::Object>> {
match self {
ObjRef::Strong(s) => Some(s.clone()),
ObjRef::Weak(w) => Weak::upgrade(w),
}
}
/// Return the [`RawAddr`] associated with this object.
fn raw_addr(&self) -> RawAddr {
match self {
ObjRef::Strong(s) => raw_addr_of(s),
ObjRef::Weak(w) => raw_addr_of_weak(w),
}
}
}
/// The raw address of an object held in an Arc or Weak.
///
/// This will be the same for every clone of an Arc, and the same for every Weak
@ -198,16 +173,16 @@ struct TaggedAddr {
addr: RawAddr,
/// The type of the object.
type_id: any::TypeId,
/// True if this is a strong reference.
///
/// TODO: We could use one of the unused lower-order bits in raw-addr to
/// avoid bloating this type.
is_strong: bool,
}
/// A generational index for [`ObjMap`].
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct GenIdx(generational_arena::Index);
pub(crate) enum GenIdx {
/// An index into the arena of weak references.
Weak(generational_arena::Index),
/// An index into the arena of strong references
Strong(generational_arena::Index),
}
/// Return the [`RawAddr`] of an arbitrary `Arc<T>`.
fn raw_addr_of<T: ?Sized>(arc: &Arc<T>) -> RawAddr {
@ -221,61 +196,38 @@ fn raw_addr_of_weak<T: ?Sized>(arc: &Weak<T>) -> RawAddr {
RawAddr(Weak::as_ptr(arc) as *const () as usize)
}
impl ArenaEntry {
/// Create a new `ArenaEntry` for a strong reference.
fn new_strong(object: Arc<dyn rpc::Object>) -> Self {
let id = (*object).type_id();
Self {
obj: ObjRef::Strong(object),
id,
}
}
/// Create a new `ArenaEntry` for a weak reference.
fn new_weak(object: &Arc<dyn rpc::Object>) -> Self {
impl WeakArenaEntry {
/// Create a new `WeakArenaEntry` for a weak reference.
fn new(object: &Arc<dyn rpc::Object>) -> Self {
let id = (**object).type_id();
Self {
obj: ObjRef::Weak(Arc::downgrade(object)),
obj: Arc::downgrade(object),
id,
}
}
/// Return true if this `ArenaEntry` is really present.
/// Return true if this `ArenaEntry` is really present.
///
/// Note that this function can produce false positives (if the entry is Weak
/// and its last strong reference is dropped in another thread), but it can
/// Note that this function can produce false positives (if the entry's
/// last strong reference is dropped in another thread), but it can
/// never produce false negatives.
fn is_present(&self) -> bool {
match &self.obj {
ObjRef::Strong(_) => true,
ObjRef::Weak(w) => {
// This is safe from false negatives because: if we can ever
// observe strong_count == 0, then there is no way for anybody
// else to "resurrect" the object.
w.strong_count() > 0
}
}
// This is safe from false negatives because: if we can ever
// observe strong_count == 0, then there is no way for anybody
// else to "resurrect" the object.
self.obj.strong_count() > 0
}
/// Return a strong reference to the object in this entry, if possible.
fn strong(&self) -> Option<Arc<dyn rpc::Object>> {
match &self.obj {
ObjRef::Strong(s) => Some(Arc::clone(s)),
ObjRef::Weak(w) => Weak::upgrade(w),
}
}
/// Return true if this is a weak reference.
fn is_weak(&self) -> bool {
matches!(&self.obj, ObjRef::Weak(_))
Weak::upgrade(&self.obj)
}
/// Return the [`TaggedAddr`] that can be used to identify this entry's object.
fn tagged_addr(&self) -> TaggedAddr {
TaggedAddr {
addr: self.obj.raw_addr(),
addr: raw_addr_of_weak(&self.obj),
type_id: self.id,
is_strong: matches!(self.obj, ObjRef::Strong(_)),
}
}
}
@ -283,48 +235,79 @@ impl ArenaEntry {
impl TaggedAddr {
/// Return the `TaggedAddr` to uniquely identify `obj` over the course of
/// its existence.
fn for_object(obj: &Arc<dyn rpc::Object>, is_strong: bool) -> Self {
fn for_object(obj: &Arc<dyn rpc::Object>) -> Self {
let type_id = (*obj).type_id();
let addr = raw_addr_of(obj);
TaggedAddr {
addr,
type_id,
is_strong,
}
TaggedAddr { addr, type_id }
}
}
/// The character we use to start all generational indices when encoded as a string.
const IDX_INDICATOR_CHAR: char = '%';
/// The character we use to separate the two parts of a generational index when
/// encoding as a string.
const IDX_SEPARATOR_CHAR: char = ':';
impl From<GenIdx> for rpc::ObjectId {
fn from(idx: GenIdx) -> Self {
let (a, b) = idx.0.into_raw_parts();
rpc::ObjectId::from(format!("{IDX_SEPARATOR_CHAR}{a}{IDX_SEPARATOR_CHAR}{b}"))
/// Encoding functions for GenIdx.
///
/// The encoding is deliberately nondeterministic: we want to avoid situations
/// where applications depend on the details of our ObjectIds, or hardcode the
/// ObjectIds they expect, or rely on the same weak generational index getting
/// encoded the same way every time they see it.
///
/// The encoding is deliberately non-cryptographic: we do not want to imply
/// that this gives any security. It is just a mild deterrent to misuse.
///
/// If you find yourself wanting to reverse-engineer this code so that you can
/// analyze these object IDs, please contact the Arti developers instead and let
/// us give you a better way to do whatever you want.
impl GenIdx {
/// Encode `self` into an rpc::ObjectId that we can give to a client.
pub(crate) fn encode(self) -> rpc::ObjectId {
self.encode_with_rng(&mut rand::thread_rng())
}
}
impl TryFrom<&rpc::ObjectId> for GenIdx {
type Error = rpc::LookupError;
/// As `encode`, but take a Rng as an argument. For testing.
fn encode_with_rng<R: rand::RngCore>(self, rng: &mut R) -> rpc::ObjectId {
use base64ct::Encoding;
use rand::Rng;
use tor_bytes::Writer;
let (weak_bit, idx) = match self {
GenIdx::Weak(idx) => (1, idx),
GenIdx::Strong(idx) => (0, idx),
};
let (a, b) = idx.into_raw_parts();
let x = rng.gen::<u64>() << 1;
let mut bytes = Vec::new();
bytes.write_u64(x | weak_bit);
bytes.write_u64((a as u64).wrapping_add(x));
bytes.write_u64(b.wrapping_sub(x));
rpc::ObjectId::from(base64ct::Base64UrlUnpadded::encode_string(&bytes[..]))
}
fn try_from(id: &rpc::ObjectId) -> Result<Self, Self::Error> {
let s = id.as_ref();
if let Some(s) = s.strip_prefix(IDX_INDICATOR_CHAR) {
if let Some((a_str, b_str)) = s.split_once(IDX_SEPARATOR_CHAR) {
let a = a_str
.parse()
.map_err(|_| rpc::LookupError::NoObject(id.clone()))?;
let b = b_str
.parse()
.map_err(|_| rpc::LookupError::NoObject(id.clone()))?;
return Ok(GenIdx(generational_arena::Index::from_raw_parts(a, b)));
}
/// Attempt to decode `id` into a `GenIdx` than an ObjMap can use.
pub(crate) fn try_decode(id: &rpc::ObjectId) -> Result<Self, rpc::LookupError> {
use base64ct::Encoding;
use tor_bytes::Reader;
let bytes = base64ct::Base64UrlUnpadded::decode_vec(id.as_ref())
.map_err(|_| rpc::LookupError::NoObject(id.clone()))?;
let mut r = Reader::from_slice(&bytes);
let mut get_u64 = || {
r.take_u64()
.map_err(|_| rpc::LookupError::NoObject(id.clone()))
};
let x = get_u64()?;
let is_weak = (x & 1) == 1;
let x = x & !1;
let a = get_u64()?;
let b = get_u64()?;
r.should_be_exhausted()
.map_err(|_| rpc::LookupError::NoObject(id.clone()))?;
let a = a.wrapping_sub(x) as usize;
let b = b.wrapping_add(x);
let idx = generational_arena::Index::from_raw_parts(a, b);
if is_weak {
Ok(GenIdx::Weak(idx))
} else {
Ok(GenIdx::Strong(idx))
}
Err(rpc::LookupError::NoObject(id.clone()))
}
}
@ -334,7 +317,7 @@ impl ObjMap {
Self::default()
}
/// Reclaim unused space in this map.
/// Reclaim unused space in this map's weak arena.
///
/// This runs in `O(n)` time.
fn tidy(&mut self) {
@ -342,26 +325,26 @@ impl ObjMap {
{
self.n_tidies += 1;
}
self.arena.retain(|index, entry| {
self.weak_arena.retain(|index, entry| {
let present = entry.is_present();
if !present {
// For everything we are removing from the `arena`, we must also
// remove it from `reverse_map`.
let ptr = entry.tagged_addr();
let found = self.reverse_map.remove(&ptr);
debug_assert_eq!(found, Some(GenIdx(index)));
debug_assert_eq!(found, Some(index));
}
present
});
}
/// If needed, clean this arena and resize it.
/// If needed, clean the weak arena and resize it.
///
/// (We call this whenever we're about to add an entry. This ensures that
/// our insertion operations run in `O(1)` time.)
fn adjust_size(&mut self) {
// If we're about to fill the arena...
if self.arena.len() >= self.arena.capacity() {
if self.weak_arena.len() >= self.weak_arena.capacity() {
// ... we delete any dead `Weak` entries.
self.tidy();
// Then, if the arena is still above half-full, we double the
@ -371,77 +354,74 @@ impl ObjMap {
// entries, or else we might re-run tidy() too soon. But we don't
// want to grow the arena if tidy() removed _most_ entries, or some
// normal usage patterns will lead to unbounded growth.)
if self.arena.len() > self.arena.capacity() / 2 {
self.arena.reserve(self.arena.capacity());
if self.weak_arena.len() > self.weak_arena.capacity() / 2 {
self.weak_arena.reserve(self.weak_arena.capacity());
}
}
}
/// Ensure that there is a strong entry for `value` in self (by inserting it
/// as needed), and return its index.
/// Unconditionally insert a strong entry for `value` in self, and return its index.
pub(crate) fn insert_strong(&mut self, value: Arc<dyn rpc::Object>) -> GenIdx {
let ptr = TaggedAddr::for_object(&value, true);
if let Some(idx) = self.reverse_map.get(&ptr) {
if let Some(entry) = self.arena.get_mut(idx.0) {
debug_assert!(entry.tagged_addr() == ptr);
return *idx;
}
}
self.adjust_size();
let idx = GenIdx(self.arena.insert(ArenaEntry::new_strong(value)));
self.reverse_map.insert(ptr, idx);
idx
GenIdx::Strong(self.strong_arena.insert(value))
}
/// Ensure that there is an entry for `value` in self, and return its index.
/// Ensure that there is a weak entry for `value` in self, and return an
/// index for it.
/// If there is no entry, create a weak entry.
#[allow(clippy::needless_pass_by_value)] // TODO: Decide whether to make this take a reference.
pub(crate) fn insert_weak(&mut self, value: Arc<dyn rpc::Object>) -> GenIdx {
let ptr = TaggedAddr::for_object(&value, false);
let ptr = TaggedAddr::for_object(&value);
if let Some(idx) = self.reverse_map.get(&ptr) {
#[cfg(debug_assertions)]
match self.arena.get(idx.0) {
match self.weak_arena.get(*idx) {
Some(entry) => debug_assert!(entry.tagged_addr() == ptr),
None => panic!("Found a dangling reference"),
}
return *idx;
return GenIdx::Weak(*idx);
}
self.adjust_size();
let idx = GenIdx(self.arena.insert(ArenaEntry::new_weak(&value)));
let idx = self.weak_arena.insert(WeakArenaEntry::new(&value));
self.reverse_map.insert(ptr, idx);
idx
GenIdx::Weak(idx)
}
/// Return the entry from this ObjMap for `idx`.
pub(crate) fn lookup(&self, idx: GenIdx) -> Option<Arc<dyn rpc::Object>> {
self.arena.get(idx.0).and_then(ArenaEntry::strong)
match idx {
GenIdx::Weak(idx) => self.weak_arena.get(idx).and_then(WeakArenaEntry::strong),
GenIdx::Strong(idx) => self.strong_arena.get(idx).map(Arc::clone),
}
}
/// Remove the entry at `idx`, if any.
pub(crate) fn remove(&mut self, idx: GenIdx) {
if let Some(entry) = self.arena.remove(idx.0) {
let old_idx = self.reverse_map.remove(&entry.tagged_addr());
debug_assert_eq!(old_idx, Some(idx));
match idx {
GenIdx::Weak(idx) => {
if let Some(entry) = self.weak_arena.remove(idx) {
let old_idx = self.reverse_map.remove(&entry.tagged_addr());
debug_assert_eq!(old_idx, Some(idx));
}
}
GenIdx::Strong(idx) => {
self.strong_arena.remove(idx);
}
}
}
/// Testing only: Assert that every invariant for this structure is met.
#[cfg(test)]
fn assert_okay(&self) {
for (index, entry) in self.arena.iter() {
for (index, entry) in self.weak_arena.iter() {
let ptr = entry.tagged_addr();
assert_eq!(self.reverse_map.get(&ptr), Some(&GenIdx(index)));
assert_eq!(self.reverse_map.get(&ptr), Some(&index));
assert_eq!(ptr, entry.tagged_addr());
}
for (ptr, idx) in self.reverse_map.iter() {
let entry = self
.arena
.get(idx.0)
.weak_arena
.get(*idx)
.expect("Dangling pointer in reverse map");
assert_eq!(&entry.tagged_addr(), ptr);
@ -528,52 +508,52 @@ mod test {
let wrapped_weak = Arc::downgrade(&wrapped_dyn);
assert_eq!(
TaggedAddr::for_object(&object_dyn, true),
TaggedAddr::for_object(&object_dyn2, true)
TaggedAddr::for_object(&object_dyn),
TaggedAddr::for_object(&object_dyn2)
);
assert_ne!(
TaggedAddr::for_object(&object_dyn, true),
TaggedAddr::for_object(&object2, true)
TaggedAddr::for_object(&object_dyn),
TaggedAddr::for_object(&object2)
);
assert_eq!(
TaggedAddr::for_object(&wrapped_dyn, true),
TaggedAddr::for_object(&wrapped_dyn2, true)
TaggedAddr::for_object(&wrapped_dyn),
TaggedAddr::for_object(&wrapped_dyn2)
);
assert_ne!(
TaggedAddr::for_object(&object_dyn, true),
TaggedAddr::for_object(&wrapped_dyn, true)
TaggedAddr::for_object(&object_dyn),
TaggedAddr::for_object(&wrapped_dyn)
);
assert_eq!(
TaggedAddr::for_object(&object_dyn, true).addr,
TaggedAddr::for_object(&wrapped_dyn, true).addr
TaggedAddr::for_object(&object_dyn).addr,
TaggedAddr::for_object(&wrapped_dyn).addr
);
assert_eq!(
TaggedAddr::for_object(&wrapped_dyn, true).addr,
TaggedAddr::for_object(&wrapped_dyn).addr,
raw_addr_of_weak(&wrapped_weak)
);
assert_eq!(
TaggedAddr::for_object(&object_dyn, true).type_id,
TaggedAddr::for_object(&object_dyn).type_id,
any::TypeId::of::<ExampleObject>()
);
assert_eq!(
TaggedAddr::for_object(&wrapped_dyn, true).type_id,
TaggedAddr::for_object(&wrapped_dyn).type_id,
any::TypeId::of::<Wrapper>()
);
assert_eq!(
TaggedAddr::for_object(&object_dyn, true).addr,
TaggedAddr::for_object(&object_dyn).addr,
raw_addr_of(&object)
);
assert_eq!(
TaggedAddr::for_object(&wrapped_dyn, true).addr,
TaggedAddr::for_object(&wrapped_dyn).addr,
raw_addr_of(&wrapped)
);
assert_ne!(
TaggedAddr::for_object(&object_dyn, true).addr,
TaggedAddr::for_object(&object_dyn).addr,
raw_addr_of(&object2)
);
}
@ -586,9 +566,11 @@ mod test {
map.assert_okay();
let id1 = map.insert_strong(obj1.clone());
let id2 = map.insert_strong(obj1.clone());
assert_eq!(id1, id2);
let obj_out = map.lookup(id1).unwrap();
assert_eq!(raw_addr_of(&obj1), raw_addr_of(&obj_out));
assert_ne!(id1, id2);
let obj_out1 = map.lookup(id1).unwrap();
let obj_out2 = map.lookup(id2).unwrap();
assert_eq!(raw_addr_of(&obj1), raw_addr_of(&obj_out1));
assert_eq!(raw_addr_of(&obj1), raw_addr_of(&obj_out2));
map.assert_okay();
}
@ -664,7 +646,7 @@ mod test {
}
{
assert_eq!(id1, map.insert_strong(obj1.clone()));
assert_ne!(id1, map.insert_strong(obj1.clone()));
assert_ne!(id2, map.insert_strong(obj2.clone()));
}
}
@ -697,6 +679,7 @@ mod test {
#[test]
fn tidy() {
let mut map = ObjMap::new();
let mut keep_these = vec![];
let mut s = vec![];
let mut w = vec![];
for _ in 0..100 {
@ -706,7 +689,9 @@ mod test {
w.push(map.insert_weak(o.clone()));
t.push(o);
}
s.push(map.insert_strong(Arc::new(ExampleObject("cafe".into()))));
let obj = Arc::new(ExampleObject("cafe".into()));
keep_these.push(obj.clone());
s.push(map.insert_weak(obj));
drop(t);
map.assert_okay();
}
@ -716,11 +701,11 @@ mod test {
assert!(w.iter().all(|id| map.lookup(*id).is_none()));
assert!(s.iter().all(|id| map.lookup(*id).is_some()));
assert_ne!(dbg!(map.arena.len()), 1100);
assert_ne!(map.weak_arena.len() + map.strong_arena.len(), 1100);
map.assert_okay();
map.tidy();
map.assert_okay();
assert_eq!(map.arena.len(), 100);
assert_eq!(map.weak_arena.len() + map.strong_arena.len(), 100);
// This number is a bit arbitrary.
assert!(dbg!(map.n_tidies) < 30);
@ -735,6 +720,34 @@ mod test {
let mut map = ObjMap::new();
map.insert_strong(obj);
map.insert_strong(wrap);
assert_eq!(map.arena.len(), 2);
assert_eq!(map.strong_arena.len(), 2);
}
#[test]
fn objid_encoding() {
use rand::Rng;
fn test_roundtrip(a: usize, b: u64, rng: &mut tor_basic_utils::test_rng::TestingRng) {
let idx = generational_arena::Index::from_raw_parts(a, b);
let idx = if rng.gen_bool(0.5) {
GenIdx::Strong(idx)
} else {
GenIdx::Weak(idx)
};
let s1 = idx.encode_with_rng(rng);
let s2 = idx.encode_with_rng(rng);
assert_ne!(s1, s2);
assert_eq!(idx, GenIdx::try_decode(&s1).unwrap());
assert_eq!(idx, GenIdx::try_decode(&s2).unwrap());
}
let mut rng = tor_basic_utils::test_rng::testing_rng();
test_roundtrip(0, 0, &mut rng);
test_roundtrip(0, 1, &mut rng);
test_roundtrip(1, 0, &mut rng);
test_roundtrip(0xffffffff, 0xffffffffffffffff, &mut rng);
for _ in 0..256 {
test_roundtrip(rng.gen(), rng.gen(), &mut rng);
}
}
}

View File

@ -130,7 +130,7 @@ If an Object is not visible in a session,
that session cannot access it.
Clients identify each Object within a session
by an opaque "Object Identifier".
by an opaque string, called an "Object Identifier".
Each identifier may be a "handle" or a "reference".
If a session has a _handle_ to an Object,
Arti won't deliberately discard that Object
@ -140,8 +140,9 @@ If a session only has a _reference_ to an Object, however,
that Object might be closed or discarded in the background,
and there is no need to release it.
> For more on how this is implemented,
> see "Representing Object Identifiers" below.
The format of an Object Identifier string is not stable,
and clients must not rely on it.
## Request and response types
@ -496,54 +497,15 @@ Therefore a client which sends more than one request at a time
must be prepared to buffer requests at its end,
while concurrently reading arti's replies.
## Representing Object Identifiers.
> This section describes implementation techniques.
> Applications should not need to care about it.
Here are two ways to provide our Object visibility semantics.
Applications should not care which one Arti uses.
Arti may use both methods for different Objects
in the same session.
In one method,
we use a generational index for each live session
to hold reference-counted pointers
to the Objects visible in the session.
The generational index is the identifier for the Object.
(This method is suitable for representing _handles_
as described above.)
In another method,
when it is more convenient for Arti to access an Object
by a global identifier `GID`,
we use a string `GID:MAC(N_s,GID)` for the Object's Identifier,
where `N_s` is a per-session secret nonce
that Arti generates and does not share with the application.
Arti verifies that the MAC is correct
before looking up the Object by its GID.
(This method is suitable for representing _references_ as
described above.)
Finally, in either method, we use a single fixed identifier
(e.g. `session`)
for the current session.
## Authentication
When a connection is first opened,
only authentication requests may be use
until authentication is successful.
only a single "connection" object is available.
Its object ID is "`connection`".
The client must authenticate to the connection
in order to receive any other object IDs.
> TODO: Perhaps it would be a good idea to say
> that when a connection is opened,
> there is an authentication Object (not a session Object)
> and only _that Object_ can be used
> until one of its responses eventually gives the application
> a session Object?
The authentication schemes are:
The pre-authentication methods available on a connection are:
auth:get_proto
: Ask Arti which version of the protocol is in use.
@ -814,10 +776,10 @@ The echo command will only work post-authentication.
Here is an example session:
```
C: { "id":3, "obj": "session", "method":"auth:authenticate", "params": {"method": "inherent:unix_path"} }
S: {"id":3,"result":{}}
C: { "id":7, "obj": "session", "method":"echo", "params": {"msg": "Hello World"} }
S: {"id":7,"result":{"msg":"Hello World"}}
>>> {"id": 3, "obj": "connection", "method": "auth:authenticate", "params": {"scheme": "inherent:unix_path"}}
<<< {"id":3,"result":{"client":"dTewFIaZKQV1N7AUhpkpBIrIT-t5Ztb8"}}
>>> {"id": 4, "obj": "dTewFIaZKQV1N7AUhpkpBIrIT-t5Ztb8", "method": "arti:x-echo", "params": {"msg": "Hello World"}}
<<< {"id":4,"result":{"msg":"Hello World"}}
```
Note that the server will currently close your connection