10 Commits
Author SHA1 Message Date
swaits e2d14bcc0d doc: add link to ticket tracker
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-04 10:48:28 -07:00
swaits 25e0076c8d doc: add link to docs.rs in README
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-04 10:43:36 -07:00
swaits aba483c9e1 bump crate version
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-04 10:40:57 -07:00
swaits e9febc6faa clean up export, add module level docs
- public NetworkCoordinate so it’s easer to just `use vivaldi_nc::NetworkCoordinate`
- add module level docs and examples in lib.rs
- cleanup README

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-04 10:39:59 -07:00
swaits c83a4e654b update README, bump crate version
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-04 09:48:01 -07:00
swaits 2fad2e18b5 feat: add serde
Adds serialization/deserialization support via serde and serde_with.

Also:

- remove `license-file` in Cargo.toml (superfluous)
- update README with Getting Started section
- add `serde` and `serde_with` dependencies to README
- add tests to `HeightVector`
- tweak assertion in `NetworkCoordinate`
- refactor `Vector` to be a struct witn an `inner` instead of a tuple struct so that we can use `serde_as` (from `serde_with`) to help us derive SerDe implementations for the const generic sized array
- moved trait requirements (which are getting long) into where clauses

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-04 09:42:46 -07:00
swaits 3415347072 remove Cargo.lock and add to .gitignore
Because this is a library, Cargo.lock should not be committed.

Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-04 09:37:40 -07:00
swaits 5611618126 prep Cargo.toml for publication, add badges
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-02 22:33:15 -07:00
swaits c2399c5b8f add build badge to readme
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-02 21:56:18 -07:00
swaits f35680738c Add build script for git.sr.ht
Signed-off-by: Stephen Waits <steve@waits.net>
2023-02-02 21:48:35 -07:00
9 changed files with 396 additions and 243 deletions

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
image: archlinux
packages:
- rustup
sources:
- https://git.sr.ht/~swaits/vivaldi-nc
tasks:
- setup: |
rustup toolchain install nightly stable
cd vivaldi-nc/
rustup run stable cargo fetch
- stable: |
rustup default stable
cd vivaldi-nc/
cargo build --verbose
cargo test --verbose
- nightly: |
rustup default nightly
cd vivaldi-nc/
cargo build --verbose ||:
cargo test --verbose ||:
- docs: |
cd vivaldi-nc/
rustup run stable cargo doc --no-deps
rustup run nightly cargo doc --no-deps ||:
+1
View File
@@ -1 +1,2 @@
/target
Cargo.lock
Generated
-192
View File
@@ -1,192 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "assert_approx_eq"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
"getrandom",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "proc-macro2"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "vivaldi-nc"
version = "0.1.0"
dependencies = [
"assert_approx_eq",
"nanorand",
"num-traits",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+10 -3
View File
@@ -1,16 +1,23 @@
[package]
name = "vivaldi-nc"
version = "0.1.0"
version = "0.3.1"
edition = "2021"
authors = ["Stephen Waits <steve@waits.net>"]
description = "An implementation of Vivaldi network coordinates."
description = "Vivaldi network coordinates with a simple interface."
license = "MIT"
keywords = ["vivaldi", "network coordinates", "distributed", "p2p"]
keywords = ["vivaldi", "network", "coordinates", "distributed", "p2p"]
categories = ["algorithms", "network-programming"]
documentation = "https://docs.rs/vivaldi-nc/"
readme = "README.md"
homepage = "https://sr.ht/~swaits/vivaldi-nc/"
repository = "https://git.sr.ht/~swaits/vivaldi-nc"
[dev-dependencies]
assert_approx_eq = "1.1.0"
serde_json = "1.0.91"
[dependencies]
nanorand = {version = "0.7.0", features = ["wyrand", "getrandom"]}
num-traits = "0.2.15"
serde = {version = "1.0.152", features = ["serde_derive"]}
serde_with = "2.2.0"
+120 -25
View File
@@ -1,5 +1,7 @@
# vivaldi-nc - Vivaldi Network Coordinates
[![docs.rs](https://img.shields.io/docsrs/vivaldi-nc)](https://docs.rs/vivaldi-nc/) [![builds.sr.ht status](https://builds.sr.ht/~swaits/vivaldi-nc.svg)](https://builds.sr.ht/~swaits/vivaldi-nc?) [![Crates.io](https://img.shields.io/crates/v/vivaldi-nc)](https://crates.io/crates/vivaldi-nc) ![Crates.io](https://img.shields.io/crates/d/vivaldi-nc) ![Crates.io](https://img.shields.io/crates/l/vivaldi-nc)
## Introduction
Network Coordinates (NC) are a way to represent a node's position in a network's
@@ -8,11 +10,11 @@ other will be close to each other. Nodes with high latency between them will be
far from each other.
This is an implementation of Vivaldi Network Coordinates, a specific NC
algorithm, with a public simple interface and few dependencies. Vivaldi
coordinates are typically used in distributed networking applications, like p2p
networks. They allow each node in the network to estimate its position in a
latency space in a distributed way, with no central authority. This enables
nodes to understand estimated latency to any other node in the system.
algorithm, with a simple interface and few dependencies. Vivaldi coordinates
are typically used in distributed networking applications, like p2p networks.
They allow each node in the network to estimate its position in a latency space
in a distributed way, with no central authority. This enables nodes to
understand estimated latency to any other node in the system.
According to the [Vivaldi coordinates article on Wikipedia](https://en.wikipedia.org/wiki/Vivaldi_coordinates):
@@ -60,9 +62,53 @@ Regarding the two drawbacks mentioned by the Wikiepedia article, quoted above:
### Getting Started
### Features
Add the crate to your project:
### Examples
```bash
cargo add vivaldi-nc
```
Each node in the network should create a `NetworkCoordinate` (NC). The node
uses this structure to track its latency position in the entire network.
```rust
use vivaldi_nc::NetworkCoordinate;
// create a 2-dimensional NetworkCoordinate to track my position
let mut my_position = NetworkCoordinate::<2>::new();
```
Normally 2 or 3-dimensions is plenty. Higher dimensions may add some accuracy,
but not enough to be worth the extra costs.
Each node occasionally sends its NC to other nodes. Upon receiving a NC from a
remote node, update your local node's position with the actual measured ping
time to that node.
```rust
my_position.update(&remote_position, Duration::from_millis(measured_rtt));
```
Over time your NC will get more and more accurate as it's updated against more
nodes in the network.
You can estimate your ping time with any other NC you receive, even if it was
forwarded to you indirectly.
```rust
let rtt_estimate = my_position.estimate_rtt(&remote_position);
```
That's the entire interface for creating and iteratively updating NCs.
If you want to save/restore NCs, or send/receive them over a network, you'll
want to serialize/deserialize them. `NetworkCoordinate` supports
[Serde](https://crates.io/crates/serde) by default. Many formats are supported
by Serde, including text formats like [JSON](https://github.com/serde-rs/json)
and compact binary formats like [bincode](https://crates.io/crates/bincode) and
[MessagePack](https://github.com/3Hren/msgpack-rust).
See the [module documentation](https://docs.rs/vivaldi-nc) for more detailed
usage examples.
## Dependencies
@@ -70,21 +116,27 @@ One design goal of this crate is to minimize dependencies. When dependencies
are required, I try to be very selective about them (re: bloat and licensing).
This crate depends on:
- `nanorand`: A fast PRNG based on WyRand. It pulls in `getrandom` as a
portable source of entropy. I chose this over the more commonly used, but
heavyweight `rand` because it's significantly smaller and does just what I
need, and not much more.
- `num-traits`: A brilliant crate which makes it easier to operate on numbers
in generics; like using `Float` as a constraint on a generic type. Its
convenience outweighs its cost.
- [`nanorand`](https://crates.io/crates/nanorand): A fast PRNG based on WyRand.
It pulls in `getrandom` as a portable source of entropy. I chose this over
the more commonly used, but heavyweight `rand` because it's significantly
smaller and does just what I need, and not much more.
- [`num-traits`](https://crates.io/crates/num-traits): A brilliant crate which
makes it easier to operate on numbers in generics; like using `Float` as a
constraint on a generic type. Its convenience outweighs its cost.
- [`serde`](https://crates.io/crates/serde): I think NCs are usually meant to
be shared across a network. That requires serialization/deserialization and
serde is *the* choice for that. It might be big, but it's efficient.
- [`serde_with`](https://crates.io/crates/serde_with): Because the inner
`Vector<T,N>` uses a const generic length, we use this to help derive
`Deserialize`.
## Alternative implementations
## Design Goals & Alternatives
Several crates implemented Vivaldi NC before this one. So, why another?
I had several design goals which the existing crates didn't satisfy:
1. Provide the simplest interface possible. I just want to have some sort of
1. Provide the *simplest interface possible*. I just want to have some sort of
Coordinate struct and be able to update it, and then use it to estimate
round trip times (ie ping times).
2. Don't require the consumer to bring their own vector or linear algebra
@@ -96,14 +148,16 @@ I had several design goals which the existing crates didn't satisfy:
rigamarole. Support `serde` traits by default.
4. Well documented. Well tested. This varies by the crates.
All of that said, those other implementations might work best for you. Here are
the ones I know of today:
### Other Vivaldi NC Implementations
- TODO: list crates
- TODO: list crates
- TODO: list crates
All of that said, those other rust implementations might work best for you.
Here are the ones I know of today:
## Other Algorithms
- [netloc](https://github.com/dgtony/netloc)
- [violin](https://crates.io/crates/violin)
- [vivaldi](https://crates.io/crates/vivaldi)
### Other NC Algorithms
Vivaldi is about the simplest distributed NC algorithm out there. That
simplicity combined with its reasonably good performance is a reason why it's
@@ -111,6 +165,47 @@ popular.
But it's far from the only choice. Here are links to other NC algorithms:
- TODO: list algorithms
- TODO: list algorithms
- TODO: list algorithms
- [Pharos](https://en.wikipedia.org/wiki/Pharos_Network_Coordinates)
- [Phoenix](https://en.wikipedia.org/wiki/Phoenix_Network_Coordinates)
Search your favorite research paper index for "network coordinates" and you'll
find many more.
## Getting Help or Contributing
To get help or discuss this crate, [submit a
ticket](https://todo.sr.ht/~swaits/vivaldi-nc) or post on
[`vivaldi-nc-discuss@`](https://lists.sr.ht/~swaits/vivaldi-nc-discuss).
Discussion related to development or patch submissions should go to
[`vivaldi-nc-devel@`](https://lists.sr.ht/~swaits/vivaldi-nc-devel).
Patchsets should use [git-send-email](https://git-send-email.io/) or the [sr.ht
UI](https://git.sr.ht/~swaits/vivaldi-nc/send-email) (easiest IMO).
Patch submitters implicitly agree that all contributions they submit fall under
the MIT license.
## License
> MIT License
>
> Copyright (c) 2023 Stephen Waits
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
+66 -1
View File
@@ -45,6 +45,7 @@
use std::ops::{Add, Mul, Sub};
use nanorand::{Rng, WyRand};
use serde::{Deserialize, Serialize};
use crate::vector::Vector;
@@ -61,7 +62,7 @@ use crate::vector::Vector;
/// ## Generic Parameters
///
/// - `N`: the dimensionality of the vector portion (i.e. non-height) of the Vivaldi height vector
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub(crate) struct HeightVector<const N: usize>(Vector<f32, N>, f32);
//
@@ -160,4 +161,68 @@ mod tests {
let a = HeightVector::<3>::from(([1.0, 2.0, 3.0], 4.0));
assert_approx_eq!(a.len(), 7.741_657, 0.00001);
}
#[test]
fn test_new() {
// new gives us a random unit length vec
let a = HeightVector::<3>::new();
assert_approx_eq!(a.len(), 1.0);
}
#[test]
fn test_default() {
// default() should give us a zero vec
let a = HeightVector::<3>::default();
assert_approx_eq!(a.len(), 0.0);
}
#[test]
fn test_from() {
let a = HeightVector::<2>::from(([1.0, 2.0], 3.0));
assert_eq!(a.0[0], 1.0);
assert_eq!(a.0[1], 2.0);
assert_eq!(a.1, 3.0);
}
#[test]
fn test_add() {
let a = HeightVector::<2>::from(([1.0, 2.0], 3.0));
let b = HeightVector::<2>::from(([3.0, 2.0], 1.0));
let c = a + b;
assert_eq!(c.0[0], 4.0);
assert_eq!(c.0[1], 4.0);
assert_eq!(c.1, 4.0);
}
#[test]
fn test_sub() {
let a = HeightVector::<2>::from(([1.0, 2.0], 3.0));
let b = HeightVector::<2>::from(([3.0, 2.0], 1.0));
let c = a - b;
assert_eq!(c.0[0], -2.0);
assert_eq!(c.0[1], 0.0);
assert_eq!(c.1, 4.0);
}
#[test]
fn test_mul_scalar() {
let a = HeightVector::<2>::from(([1.0, 2.0], 3.0)) * 10.0;
assert_eq!(a.0[0], 10.0);
assert_eq!(a.0[1], 20.0);
assert_eq!(a.1, 30.0);
}
#[test]
fn test_serde() {
// start with JSON, deserialize it
let s = "[{\"inner\":[1.0,2.0,3.0]},4.0]";
let a: HeightVector<3> = serde_json::from_str(s).unwrap();
// make sure it's the right length and works like we expect a normal NC
assert_approx_eq!(a.len(), 7.741_657, 0.001);
// serialize it into a new JSON string and make sure it matches the original
let t = serde_json::to_string(&a);
assert_eq!(t.as_ref().unwrap(), s);
}
}
+92
View File
@@ -1,4 +1,96 @@
//! Vivaldi Network Coordinates with a simple interface
//!
//! Network Coordinates (NC) are a way to represent a node's position in a network's
//! latency space. Nodes with low latency (ping or round trip time) between each
//! other will be close to each other in this latency space. Nodes with high latency between them
//! will be far from each other.
//!
//! This is an implementation of Vivaldi Network Coordinates, a specific NC
//! algorithm, with a simple interface and few dependencies. Vivaldi coordinates
//! are typically used in distributed networking applications, like p2p networks.
//! They allow each node in the network to estimate its position in a latency space
//! in a distributed way, with no central authority. This enables nodes to
//! understand estimated latency to any other node in the system.
//!
//! To learn more, see the [Vivaldi coordinates article on
//! Wikipedia](https://en.wikipedia.org/wiki/Vivaldi_coordinates) or [the original
//! paper](http://conferences.sigcomm.org/sigcomm/2004/papers/p426-dabek111111.pdf) (Frank Dabek,
//! Russ Cox, Frans Kaashoek, Robert Morris (2004)).
//!
//! # Usage
//!
//! This crate exports a single struct, [`NetworkCoordinate`]. Typical use of Vivaldi NCs for a
//! distributed network works like this:
//!
//! 1. Each node in the network has its own instance of [`NetworkCoordinate`]. *See "Note on
//! dimensionality" below.*
//! 2. Every node occasionally sends its [`NetworkCoordinate`] to some or all other nodes through
//! the network overlay.
//! 3. Whenever a node receives a remote coordinate, it updates its local coordinate with the
//! actual measured round trip time (RTT or "ping time") between itself and that node to improve
//! its own position and thus reduce its estimation error.
//! 4. Any node can estimate its RTT to any other [`NetworkCoordinate`] it's aware of.
//!
//! Importantly, all updates in this system are performed locally. No central authority is needed
//! to build the network latency maps. It's fully distributed.
//!
//! **Note on dimensionality:** Typically 2D or 3D Vivaldi coordinates (i.e. `NetworkCoordinate<2>` or
//! `NetworkCoordinate<3>`) are sufficient. Higher dimensions don't add much accuracy and aren't
//! generally worth it. That said, you're welcome to use any dimension you like.
//!
//! # Examples
//!
//! In the first example, we simulate receving a remote NC, measuring ping, and updating our NC
//! accordingly.
//!
//! ```
//! use vivaldi_nc::NetworkCoordinate;
//!
//! // create our local NC as a 3-dimensional Vivaldi coordinate
//! let mut my_nc = NetworkCoordinate::<3>::new();
//!
//! // mock up a remote NC for the sake of this example (normally the node would actually receive
//! // this from a remote node)
//! let received_msg = "{\"position\":[{\"inner\":[1.5,0.5,2.0]},0.1],\"error\":1.0}";
//! let remote_nc: NetworkCoordinate<3> = serde_json::from_str(received_msg).unwrap();
//!
//! // mock up a measured RTT between us and that node (normally the nodes would coordinate
//! // measuring this together)
//! let rtt_actual = std::time::Duration::from_millis(78);
//!
//! // with the actual RTT in hand, we can update our NC to improve its accuracy
//! my_nc.update(&remote_nc, rtt_actual);
//!
//! // by doing this iteratively (and forever), all NCs in the network will converge to better
//! // represent the actual latency space of the network
//! ```
//!
//! In this second example, we simulate receving a NC from another node indirectly; likely
//! forwarded to us by someone else. And in this case, we aren't measuring the actual RTT. Instead,
//! we're using the NCs to estimate RTT.
//!
//! ```
//! use vivaldi_nc::NetworkCoordinate;
//!
//! // create our local NC as a 3-dimensional Vivaldi coordinate
//! let mut my_nc = NetworkCoordinate::<3>::new();
//!
//! // mock up a remote NC for the sake of this example (normally the node would actually receive
//! // this from a remote node)
//! let received_msg = "{\"position\":[{\"inner\":[1.5,0.5,2.0]},0.1],\"error\":1.0}";
//! let remote_nc: NetworkCoordinate<3> = serde_json::from_str(received_msg).unwrap();
//!
//! // estimate the RTT to this remote node without measuring it directly
//! let rtt_estimated = my_nc.estimated_rtt(&remote_nc);
//!
//! // so with just the knowledge of another NC, we can get reasonably good estimates of RTT
//! // without actually needing to measure it
//! ```
//!
mod height_vector;
mod vector;
// publish our interface
pub mod network_coordinate;
pub use network_coordinate::NetworkCoordinate;
+23 -5
View File
@@ -27,6 +27,8 @@
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::height_vector::HeightVector;
//
@@ -47,7 +49,7 @@ const MIN_ERROR: f32 = f32::EPSILON;
// **** Structs ****
//
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct NetworkCoordinate<const N: usize> {
position: HeightVector<N>,
error: f32,
@@ -63,7 +65,7 @@ impl<const N: usize> NetworkCoordinate<N> {
/// # Example
///
/// ```
/// use vivaldi_nc::network_coordinate::NetworkCoordinate;
/// use vivaldi_nc::NetworkCoordinate;
///
/// // create a new 3-dimensional random NC
/// let a: NetworkCoordinate<3> = NetworkCoordinate::new();
@@ -91,7 +93,7 @@ impl<const N: usize> NetworkCoordinate<N> {
/// # Example
///
/// ```
/// use vivaldi_nc::network_coordinate::NetworkCoordinate;
/// use vivaldi_nc::NetworkCoordinate;
///
/// // create some 2-dimensional NCs for the sake of this example. These will just be random
/// // NCs. In a real usecase these would have meaningful values.
@@ -123,7 +125,7 @@ impl<const N: usize> NetworkCoordinate<N> {
///
/// ```
/// use std::time::Duration;
/// use vivaldi_nc::network_coordinate::NetworkCoordinate;
/// use vivaldi_nc::NetworkCoordinate;
///
/// // We always have our own NC:
/// let mut local: NetworkCoordinate<2> = NetworkCoordinate::new();
@@ -274,6 +276,22 @@ mod tests {
(slc.error.powf(2.0) + nyc.error.powf(2.0) + lax.error.powf(2.0) + mad.error.powf(2.0))
.sqrt();
println!("error = {error}");
assert!(error < 0.07);
assert!(error < 0.2);
}
#[test]
fn test_serde() {
// start with JSON, deserialize it
let s = "{\"position\":[{\"inner\":[1.5,0.5,2.0]},0.1],\"error\":1.0}";
let a: NetworkCoordinate<3> = serde_json::from_str(s).unwrap();
// make sure it's the right length and works like we expect a normal NC
assert_approx_eq!(a.position.len(), 2.649_509, 0.001);
assert_eq!(a.error, 1.0);
assert_eq!(a.estimated_rtt(&a).as_millis(), 0);
// serialize it into a new JSON string and make sure it matches the original
let t = serde_json::to_string(&a);
assert_eq!(t.as_ref().unwrap(), s);
}
}
+60 -17
View File
@@ -4,6 +4,8 @@
//! Vivaldi `HeightVector`.
use num_traits::Float;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use std::ops::{Add, Div, Index, Mul, Sub};
@@ -16,14 +18,24 @@ use std::ops::{Add, Div, Index, Mul, Sub};
/// ## Generic parameters:
/// - `T`: the type to use, must satisfy the `Float` trait (i.e. `f32` or `f64`)
/// - `N`: the number of dimensions (i.e. 2 for 2D vectors, 3 for 3D, etc)
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct Vector<T: Float, const N: usize>([T; N]);
#[serde_as]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct Vector<T, const N: usize>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
#[serde_as(as = "[_; N]")]
inner: [T; N],
}
//
// **** Implementations ****
//
impl<T: Float, const N: usize> Vector<T, N> {
impl<T, const N: usize> Vector<T, N>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
/// Create a new Zero vector.
pub(crate) fn new() -> Self {
Self::default()
@@ -32,8 +44,8 @@ impl<T: Float, const N: usize> Vector<T, N> {
/// Compute magnitude of vector.
pub(crate) fn len(&self) -> T {
(0..N)
.map(|i| self.0[i] * self.0[i])
.fold(T::zero(), |acc, x| acc + x)
.map(|i| self.inner[i] * self.inner[i])
.fold(T::zero(), |sum_sq, x| sum_sq + x)
.sqrt()
}
}
@@ -42,21 +54,32 @@ impl<T: Float, const N: usize> Vector<T, N> {
// **** Trait Implementations ****
//
impl<T: Float, const N: usize> Default for Vector<T, N> {
impl<T, const N: usize> Default for Vector<T, N>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
/// A default vector is all zeroes
fn default() -> Self {
Self([T::zero(); N])
Self {
inner: [T::zero(); N],
}
}
}
impl<T: Float, const N: usize> From<[T; N]> for Vector<T, N> {
impl<T, const N: usize> From<[T; N]> for Vector<T, N>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
/// Create a new Vector from an array of type T, N dimensions.
fn from(value: [T; N]) -> Self {
Self(value)
Self { inner: value }
}
}
impl<T: Float, const N: usize> Add for Vector<T, N> {
impl<T, const N: usize> Add for Vector<T, N>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
type Output = Self;
/// Vector+Vector addition.
@@ -69,7 +92,10 @@ impl<T: Float, const N: usize> Add for Vector<T, N> {
}
}
impl<T: Float, const N: usize> Sub for Vector<T, N> {
impl<T, const N: usize> Sub for Vector<T, N>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
type Output = Self;
/// Vector-Vector subtraction.
@@ -82,38 +108,47 @@ impl<T: Float, const N: usize> Sub for Vector<T, N> {
}
}
impl<T: Float, const N: usize> Mul<T> for Vector<T, N> {
impl<T, const N: usize> Mul<T> for Vector<T, N>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
type Output = Self;
/// Vector*scalar multiplication.
fn mul(self, rhs: T) -> Self::Output {
let mut ret = Self::Output::new();
for i in 0..N {
ret.0[i] = self.0[i] * rhs
ret.inner[i] = self.inner[i] * rhs
}
ret
}
}
impl<T: Float, const N: usize> Div<T> for Vector<T, N> {
impl<T, const N: usize> Div<T> for Vector<T, N>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
type Output = Self;
/// Vector/scalar division.
fn div(self, rhs: T) -> Self::Output {
let mut ret = Self::Output::new();
for i in 0..N {
ret.0[i] = self.0[i] / rhs
ret.inner[i] = self.inner[i] / rhs
}
ret
}
}
impl<T: Float, const N: usize> Index<usize> for Vector<T, N> {
impl<T, const N: usize> Index<usize> for Vector<T, N>
where
T: Float + Serialize + for<'d> Deserialize<'d>,
{
type Output = T;
/// Nicer indexing [] for read only references.
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
&self.inner[index]
}
}
@@ -183,4 +218,12 @@ mod tests {
let b = a * 10.0;
assert_approx_eq!(b.len(), 37.416_57, 0.0001);
}
#[test]
fn test_serde() {
let a = Vector::<f32, 3>::from([1.0, 2.0, 3.0]);
let s = serde_json::to_string(&a);
assert_eq!(s.as_ref().unwrap(), "{\"inner\":[1.0,2.0,3.0]}");
assert!(s.is_ok());
}
}