├── .github
├── FUNDING.yml
└── workflows
│ ├── cicd.yml
│ └── doc.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE-APACHE2
├── LICENSE-MIT
├── README.md
├── flake.lock
├── flake.nix
├── lofire-broker
├── Cargo.toml
└── src
│ ├── account.rs
│ ├── auth.rs
│ ├── config.rs
│ ├── connection.rs
│ ├── lib.rs
│ ├── overlay.rs
│ ├── peer.rs
│ ├── repostoreinfo.rs
│ ├── server.rs
│ └── topic.rs
├── lofire-demo
├── Cargo.toml
└── src
│ └── main.rs
├── lofire-net
├── Cargo.toml
└── src
│ ├── errors.rs
│ ├── lib.rs
│ └── types.rs
├── lofire-node
├── Cargo.toml
└── src
│ └── main.rs
├── lofire-p2p
├── Cargo.toml
└── src
│ └── lib.rs
├── lofire-store-lmdb
├── Cargo.toml
└── src
│ ├── brokerstore.rs
│ ├── lib.rs
│ └── repostore.rs
└── lofire
├── Cargo.toml
└── src
├── block.rs
├── branch.rs
├── brokerstore.rs
├── commit.rs
├── errors.rs
├── lib.rs
├── object.rs
├── repo.rs
├── store.rs
├── types.rs
└── utils.rs
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 | - https://p2pcollab.net/donate
15 |
--------------------------------------------------------------------------------
/.github/workflows/cicd.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on: push
3 | jobs:
4 | cargo-build-test:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v3
8 | - uses: nixbuild/nix-quick-install-action@v17
9 | with:
10 | nix_conf: experimental-features = nix-command flakes
11 | - name: nix develop
12 | run: nix develop
13 | - name: cargo build
14 | run: cargo build
15 | - name: cargo test
16 | run: cargo test
17 |
18 | default-package:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v3
22 | - uses: nixbuild/nix-quick-install-action@v17
23 | with:
24 | nix_conf: experimental-features = nix-command flakes
25 | - name: default package
26 | run: nix build
27 |
28 | lofire-package:
29 | runs-on: ubuntu-latest
30 | steps:
31 | - uses: actions/checkout@v3
32 | - uses: nixbuild/nix-quick-install-action@v17
33 | with:
34 | nix_conf: experimental-features = nix-command flakes
35 | - name: lofire package
36 | run: nix build '.#lofire'
37 |
38 | lofire-broker-package:
39 | runs-on: ubuntu-latest
40 | steps:
41 | - uses: actions/checkout@v3
42 | - uses: nixbuild/nix-quick-install-action@v17
43 | with:
44 | nix_conf: experimental-features = nix-command flakes
45 | - name: lofire-broker package
46 | run: nix build '.#lofire-broker'
47 |
48 | lofire-p2p-package:
49 | runs-on: ubuntu-latest
50 | steps:
51 | - uses: actions/checkout@v3
52 | - uses: nixbuild/nix-quick-install-action@v17
53 | with:
54 | nix_conf: experimental-features = nix-command flakes
55 | - name: lofire-p2p package
56 | run: nix build '.#lofire-p2p'
57 |
58 | lofire-node-package:
59 | runs-on: ubuntu-latest
60 | steps:
61 | - uses: actions/checkout@v3
62 | - uses: nixbuild/nix-quick-install-action@v17
63 | with:
64 | nix_conf: experimental-features = nix-command flakes
65 | - name: lofire-node package
66 | run: nix build '.#lofire-node'
67 |
68 | lofire-demo-package:
69 | runs-on: ubuntu-latest
70 | steps:
71 | - uses: actions/checkout@v3
72 | - uses: nixbuild/nix-quick-install-action@v17
73 | with:
74 | nix_conf: experimental-features = nix-command flakes
75 | - name: lofire-demo package
76 | run: nix build '.#lofire-demo'
77 |
--------------------------------------------------------------------------------
/.github/workflows/doc.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Publish documentation
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: [master]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow one concurrent deployment
19 | concurrency:
20 | group: "pages"
21 | cancel-in-progress: true
22 |
23 | jobs:
24 | # Single deploy job since we're just deploying
25 | deploy:
26 | environment:
27 | name: github-pages
28 | url: ${{ steps.deployment.outputs.page_url }}
29 | runs-on: ubuntu-latest
30 | steps:
31 | - uses: actions/checkout@v3
32 | - uses: nixbuild/nix-quick-install-action@v17
33 | with:
34 | nix_conf: experimental-features = nix-command flakes
35 | - name: nix develop
36 | run: nix develop
37 | - name: cargo doc
38 | run: cargo doc --no-deps
39 | - name: public/
40 | run: mkdir public; mv target/doc public
41 | - name: public/index.html
42 | run: echo '
LoFiRe Rust DocumentationLoFiRe Rust Documentation
' >public/index.html
43 | - name: doc/index.html
44 | run: (echo 'LoFiRe Rust API DocumentationLoFiRe Rust API Documentation
'; for p in lofire*; do echo "- $p
"; done; echo '
') >public/doc/index.html
45 | - name: Setup Pages
46 | uses: actions/configure-pages@v2
47 | - name: Upload artifact
48 | uses: actions/upload-pages-artifact@v1
49 | with:
50 | path: public
51 | - name: Deploy to GitHub Pages
52 | id: deployment
53 | uses: actions/deploy-pages@v1
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .*
3 | !.github
4 | \#*
5 | /target
6 | /result*
7 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "lofire",
4 | "lofire-net",
5 | "lofire-broker",
6 | "lofire-p2p",
7 | "lofire-store-lmdb",
8 | "lofire-node",
9 | "lofire-demo",
10 | ]
--------------------------------------------------------------------------------
/LICENSE-APACHE2:
--------------------------------------------------------------------------------
1 | Copyright 2022 TG x Thoth
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright 2022 TG x Thoth
2 |
3 | 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:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | 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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LoFiRe Rust implementation
2 |
3 | ## Website
4 |
5 | [LoFiRe](https://lofi.re)
6 |
7 | ## Design & specification
8 |
9 | [LoFiRe: Local-First Repositories for Asynchronous Collaboration over Community Overlay Networks](https://lofi.re/design/lofire)
10 |
11 | ## API documentation
12 |
13 | [LoFiRe Rust API Documentation](https://p2pcollab.github.io/lofire-rs/doc/)
14 |
15 | ## Overview
16 |
17 | The following components are implemented so far:
18 |
19 | - lofire: library that allows access to the repository, branches, commits, objects, blocks, and contains a hash map backed store implementation.
20 | - lofire-store-lmdb: encrypted LMDB store implementation
21 | - lofire-net: library that provides network message types
22 | - lofire-broker: library that implements the broker server and client protocol with async, this allows running them via arbitrary network transports or in-process without networking
23 | - lofire-node: daemon that runs a websocket server and the broker protocol over it
24 | - lofire-demo: an application to demonstrate the usage and functionality that connects to the node and sends messages to it
25 |
26 | For examples on using the libraries, see the test cases and the demo application.
27 | To run the demo, first run lofire-node, then lofire-demo (see below).
28 |
29 | ## Development
30 |
31 | ### Cargo
32 |
33 | #### Build
34 |
35 | Build all packages:
36 |
37 | ```
38 | cargo build
39 | ```
40 |
41 | #### Test
42 |
43 | Test all:
44 |
45 | ```
46 | cargo test --all --verbose -- --nocapture
47 | ```
48 |
49 | Test a single module:
50 |
51 | ```
52 | cargo test --package lofire --lib -- branch::test --nocapture
53 | ```
54 |
55 | #### Documentation
56 |
57 | Generate documentation for all packages without their dependencies:
58 |
59 | ```
60 | cargo doc --no-deps
61 | ```
62 |
63 | The generated documentation can be found in `target/doc/`.
64 |
65 | #### Run
66 |
67 | Build & run executables:
68 |
69 | ```
70 | cargo run --bin lofire-node
71 | cargo run --bin lofire-demo
72 | ```
73 |
74 | ### Nix
75 |
76 | Install the [Nix package manager](https://nixos.org/download.html)
77 | and [Nix Flakes](https://nixos.wiki/wiki/Flakes)
78 |
79 | #### Development shell
80 |
81 | Get a development shell with all dependencies available:
82 |
83 | ```
84 | nix develop
85 | cargo build
86 | ...
87 | ```
88 |
89 | #### Build a package
90 |
91 | Build the default package (`.#lofire-node`):
92 |
93 | ```
94 | nix build
95 | ```
96 |
97 | Bulid a specific package:
98 |
99 | ```
100 | nix build '.#lofire'
101 | ```
102 |
103 | #### Run
104 |
105 | Run the default executable (`.#lofire-node`):
106 |
107 | ```
108 | nix run
109 | ```
110 |
111 | Run executables:
112 |
113 | ```
114 | nix run '.#lofire-node'
115 | nix run '.#lofire-demo'
116 | ```
117 |
118 | ## License
119 |
120 | Licensed under either of Apache License, Version 2.0 or MIT license at your option.
121 |
122 | Unless you explicitly stated otherwise, any contribution intentionally submitted
123 | for inclusion in this crate by you, as defined in the Apache-2.0 license,
124 | shall be dual licensed as above, without any additional terms or conditions.
125 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "locked": {
5 | "lastModified": 1656065134,
6 | "narHash": "sha256-oc6E6ByIw3oJaIyc67maaFcnjYOz1mMcOtHxbEf9NwQ=",
7 | "owner": "numtide",
8 | "repo": "flake-utils",
9 | "rev": "bee6a7250dd1b01844a2de7e02e4df7d8a0a206c",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "numtide",
14 | "repo": "flake-utils",
15 | "type": "github"
16 | }
17 | },
18 | "nixpkgs": {
19 | "locked": {
20 | "lastModified": 1657693803,
21 | "narHash": "sha256-G++2CJ9u0E7NNTAi9n5G8TdDmGJXcIjkJ3NF8cetQB8=",
22 | "owner": "nixos",
23 | "repo": "nixpkgs",
24 | "rev": "365e1b3a859281cf11b94f87231adeabbdd878a2",
25 | "type": "github"
26 | },
27 | "original": {
28 | "owner": "nixos",
29 | "ref": "nixos-22.05",
30 | "repo": "nixpkgs",
31 | "type": "github"
32 | }
33 | },
34 | "nixpkgs_2": {
35 | "locked": {
36 | "lastModified": 1656401090,
37 | "narHash": "sha256-bUS2nfQsvTQW2z8SK7oEFSElbmoBahOPtbXPm0AL3I4=",
38 | "owner": "NixOS",
39 | "repo": "nixpkgs",
40 | "rev": "16de63fcc54e88b9a106a603038dd5dd2feb21eb",
41 | "type": "github"
42 | },
43 | "original": {
44 | "owner": "NixOS",
45 | "ref": "nixpkgs-unstable",
46 | "repo": "nixpkgs",
47 | "type": "github"
48 | }
49 | },
50 | "root": {
51 | "inputs": {
52 | "nixpkgs": "nixpkgs",
53 | "rust-overlay": "rust-overlay",
54 | "utils": "utils"
55 | }
56 | },
57 | "rust-overlay": {
58 | "inputs": {
59 | "flake-utils": "flake-utils",
60 | "nixpkgs": "nixpkgs_2"
61 | },
62 | "locked": {
63 | "lastModified": 1657767064,
64 | "narHash": "sha256-Mp7LmSPnRfWqX7OElXr4HKNbTiDCXLaxijp23xQlDJk=",
65 | "owner": "oxalica",
66 | "repo": "rust-overlay",
67 | "rev": "db9443ca1384f94c0aa63f4e74115f7c0632a8e6",
68 | "type": "github"
69 | },
70 | "original": {
71 | "owner": "oxalica",
72 | "repo": "rust-overlay",
73 | "type": "github"
74 | }
75 | },
76 | "utils": {
77 | "locked": {
78 | "lastModified": 1656928814,
79 | "narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
80 | "owner": "numtide",
81 | "repo": "flake-utils",
82 | "rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
83 | "type": "github"
84 | },
85 | "original": {
86 | "owner": "numtide",
87 | "repo": "flake-utils",
88 | "type": "github"
89 | }
90 | }
91 | },
92 | "root": "root",
93 | "version": 7
94 | }
95 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "LoFiRe";
3 |
4 | inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
5 | inputs.utils.url = "github:numtide/flake-utils";
6 | inputs.rust-overlay.url = "github:oxalica/rust-overlay";
7 |
8 | outputs = {
9 | self,
10 | nixpkgs,
11 | utils,
12 | rust-overlay,
13 | }:
14 | utils.lib.eachDefaultSystem (system: let
15 | overlays = [
16 | (import rust-overlay)
17 | ];
18 | pkgs = import nixpkgs rec {
19 | inherit system overlays;
20 | };
21 | rust = pkgs.rust-bin.stable."1.62.0".default.override {
22 | extensions = ["rust-src"];
23 | };
24 | buildRustPackage =
25 | (pkgs.makeRustPlatform {
26 | cargo = rust;
27 | rustc = rust;
28 | })
29 | .buildRustPackage;
30 | myNativeBuildInputs = with pkgs;
31 | [
32 | pkgconfig
33 | ]
34 | ++ lib.optionals stdenv.isLinux
35 | (with pkgs; [
36 | cargo-kcov
37 | ]);
38 | myBuildInputs = with pkgs;
39 | [
40 | openssl
41 | ]
42 | ++ lib.optionals stdenv.isDarwin
43 | (with darwin.apple_sdk.frameworks; [
44 | Security
45 | ]);
46 | myBuildRustPackage = attrs:
47 | buildRustPackage ({
48 | version = "0.1.0";
49 | src = ./.;
50 | cargoLock = {
51 | lockFile = ./Cargo.lock;
52 | outputHashes = {
53 | "lmdb-crypto-rs-0.14.0" = "sha256-cCTQuYxhiFHQZb+OW997sis50z5XrQk+KiTeaa2mlhk=";
54 | "rkv-0.18.0" = "sha256-G0E2qC4Uie4es91aNiVZeI50SLBUc0KQQq+t08kpRIc=";
55 | };
56 | };
57 | nativeBuildInputs = myNativeBuildInputs;
58 | buildInputs = myBuildInputs;
59 | RUST_BACKTRACE = 1;
60 | }
61 | // attrs);
62 | in rec {
63 | packages = rec {
64 | lofire = myBuildRustPackage rec {
65 | pname = "lofire";
66 | buildAndTestSubdir = "./lofire";
67 | };
68 | lofire-broker = myBuildRustPackage rec {
69 | pname = "lofire-broker";
70 | buildAndTestSubdir = "./lofire-broker";
71 | };
72 | lofire-p2p = myBuildRustPackage rec {
73 | pname = "lofire-p2p";
74 | buildAndTestSubdir = "./lofire-p2p";
75 | };
76 | lofire-store-lmdb = myBuildRustPackage rec {
77 | pname = "lofire-store-lmdb";
78 | buildAndTestSubdir = "./lofire-store-lmdb";
79 | };
80 | lofire-node = myBuildRustPackage rec {
81 | pname = "lofire-node";
82 | buildAndTestSubdir = "./lofire-node";
83 | };
84 | lofire-demo = myBuildRustPackage rec {
85 | pname = "lofire-demo";
86 | buildAndTestSubdir = "./lofire-demo";
87 | };
88 | default = lofire-node;
89 | };
90 |
91 | apps = rec {
92 | lofire-node = utils.lib.mkApp {
93 | drv = packages.lofire-node;
94 | exePath = "/bin/lofire-node";
95 | };
96 | lofire-demo = utils.lib.mkApp {
97 | drv = packages.lofire-demo;
98 | exePath = "/bin/lofire-demo";
99 | };
100 | default = lofire-node;
101 | };
102 | });
103 | }
104 |
--------------------------------------------------------------------------------
/lofire-broker/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lofire-broker"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | debug_print = "1.0.0"
10 | lofire = { path = "../lofire" }
11 | lofire-net = { path = "../lofire-net" }
12 | lofire-store-lmdb = { path = "../lofire-store-lmdb" }
13 | blake3 = "1.3.1"
14 | chacha20 = "0.9.0"
15 | serde = { version = "1.0", features = ["derive"] }
16 | serde_bare = "0.5.0"
17 | serde_bytes = "0.11.7"
18 | xactor = "0.7.11"
19 | async-std = { version = "1.7.0", features = ["attributes"] }
20 | async-trait = "0.1.57"
21 | async-broadcast = "0.4.1"
22 | futures = "0.3.24"
23 | async-oneshot = "0.5.0"
24 | rust-fsm = "0.6.0"
25 | getrandom = "0.2.7"
26 | async-channel = "1.7.1"
27 | tempfile = "3"
28 | hex = "0.4.3"
29 |
--------------------------------------------------------------------------------
/lofire-broker/src/account.rs:
--------------------------------------------------------------------------------
1 | //! User account
2 |
3 | use lofire::brokerstore::BrokerStore;
4 | use lofire::store::*;
5 | use lofire::types::*;
6 | use lofire_net::types::*;
7 | use serde_bare::to_vec;
8 |
9 | pub struct Account<'a> {
10 | /// User ID
11 | id: UserId,
12 | store: &'a dyn BrokerStore,
13 | }
14 |
15 | impl<'a> Account<'a> {
16 | const PREFIX: u8 = b"u"[0];
17 |
18 | // propertie's suffixes
19 | const CLIENT: u8 = b"c"[0];
20 | const ADMIN: u8 = b"a"[0];
21 | const OVERLAY: u8 = b"o"[0];
22 |
23 | const ALL_PROPERTIES: [u8; 3] = [Self::CLIENT, Self::ADMIN, Self::OVERLAY];
24 |
25 | const SUFFIX_FOR_EXIST_CHECK: u8 = Self::ADMIN;
26 |
27 | pub fn open(id: &UserId, store: &'a dyn BrokerStore) -> Result, StorageError> {
28 | let opening = Account {
29 | id: id.clone(),
30 | store,
31 | };
32 | if !opening.exists() {
33 | return Err(StorageError::NotFound);
34 | }
35 | Ok(opening)
36 | }
37 | pub fn create(
38 | id: &UserId,
39 | admin: bool,
40 | store: &'a dyn BrokerStore,
41 | ) -> Result, StorageError> {
42 | let acc = Account {
43 | id: id.clone(),
44 | store,
45 | };
46 | if acc.exists() {
47 | return Err(StorageError::BackendError);
48 | }
49 | store.put(
50 | Self::PREFIX,
51 | &to_vec(&id)?,
52 | Some(Self::ADMIN),
53 | to_vec(&admin)?,
54 | )?;
55 | Ok(acc)
56 | }
57 | pub fn exists(&self) -> bool {
58 | self.store
59 | .get(
60 | Self::PREFIX,
61 | &to_vec(&self.id).unwrap(),
62 | Some(Self::SUFFIX_FOR_EXIST_CHECK),
63 | )
64 | .is_ok()
65 | }
66 | pub fn id(&self) -> UserId {
67 | self.id
68 | }
69 | pub fn add_client(&self, client: &ClientId) -> Result<(), StorageError> {
70 | if !self.exists() {
71 | return Err(StorageError::BackendError);
72 | }
73 | self.store.put(
74 | Self::PREFIX,
75 | &to_vec(&self.id)?,
76 | Some(Self::CLIENT),
77 | to_vec(client)?,
78 | )
79 | }
80 | pub fn remove_client(&self, client: &ClientId) -> Result<(), StorageError> {
81 | self.store.del_property_value(
82 | Self::PREFIX,
83 | &to_vec(&self.id)?,
84 | Some(Self::CLIENT),
85 | to_vec(client)?,
86 | )
87 | }
88 |
89 | pub fn has_client(&self, client: &ClientId) -> Result<(), StorageError> {
90 | self.store.has_property_value(
91 | Self::PREFIX,
92 | &to_vec(&self.id)?,
93 | Some(Self::CLIENT),
94 | to_vec(client)?,
95 | )
96 | }
97 |
98 | pub fn add_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
99 | if !self.exists() {
100 | return Err(StorageError::BackendError);
101 | }
102 | self.store.put(
103 | Self::PREFIX,
104 | &to_vec(&self.id)?,
105 | Some(Self::OVERLAY),
106 | to_vec(overlay)?,
107 | )
108 | }
109 | pub fn remove_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
110 | self.store.del_property_value(
111 | Self::PREFIX,
112 | &to_vec(&self.id)?,
113 | Some(Self::OVERLAY),
114 | to_vec(overlay)?,
115 | )
116 | }
117 |
118 | pub fn has_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
119 | self.store.has_property_value(
120 | Self::PREFIX,
121 | &to_vec(&self.id)?,
122 | Some(Self::OVERLAY),
123 | to_vec(overlay)?,
124 | )
125 | }
126 |
127 | pub fn is_admin(&self) -> Result {
128 | if self
129 | .store
130 | .has_property_value(
131 | Self::PREFIX,
132 | &to_vec(&self.id)?,
133 | Some(Self::ADMIN),
134 | to_vec(&true)?,
135 | )
136 | .is_ok()
137 | {
138 | return Ok(true);
139 | }
140 | Ok(false)
141 | }
142 |
143 | pub fn del(&self) -> Result<(), StorageError> {
144 | self.store
145 | .del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)
146 | }
147 | }
148 |
149 | #[cfg(test)]
150 | mod test {
151 |
152 | use lofire::store::*;
153 | use lofire::types::*;
154 | use lofire::utils::*;
155 | use lofire_store_lmdb::brokerstore::LmdbBrokerStore;
156 | use std::fs;
157 | use tempfile::Builder;
158 |
159 | use crate::account::Account;
160 |
161 | #[test]
162 | pub fn test_account() {
163 | let path_str = "test-env";
164 | let root = Builder::new().prefix(path_str).tempdir().unwrap();
165 | let key: [u8; 32] = [0; 32];
166 | fs::create_dir_all(root.path()).unwrap();
167 | println!("{}", root.path().to_str().unwrap());
168 | let mut store = LmdbBrokerStore::open(root.path(), key);
169 |
170 | let user_id = PubKey::Ed25519PubKey([1; 32]);
171 |
172 | let account = Account::create(&user_id, true, &store).unwrap();
173 | println!("account created {}", account.id());
174 |
175 | let account2 = Account::open(&user_id, &store).unwrap();
176 | println!("account opened {}", account2.id());
177 |
178 | let client_id = PubKey::Ed25519PubKey([56; 32]);
179 | let client_id_not_added = PubKey::Ed25519PubKey([57; 32]);
180 |
181 | account2.add_client(&client_id).unwrap();
182 |
183 | assert!(account2.is_admin().unwrap());
184 |
185 | account.has_client(&client_id).unwrap();
186 | assert!(account.has_client(&client_id_not_added).is_err());
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/lofire-broker/src/auth.rs:
--------------------------------------------------------------------------------
1 | use std::pin::Pin;
2 |
3 | use debug_print::*;
4 | use futures::future::BoxFuture;
5 | use futures::future::OptionFuture;
6 | use futures::FutureExt;
7 | use lofire::types::*;
8 | use lofire::utils::*;
9 | use lofire_net::errors::*;
10 | use lofire_net::types::*;
11 | use rust_fsm::*;
12 |
13 | state_machine! {
14 | derive(Debug)
15 | AuthProtocolClient(Ready)
16 |
17 | Ready(ClientHelloSent) => ClientHelloSent,
18 | ClientHelloSent(ServerHelloReceived) => ServerHelloReceived,
19 | ServerHelloReceived(ClientAuthSent) => ClientAuthSent,
20 | ClientAuthSent(AuthResultReceived) => AuthResult,
21 | AuthResult => {
22 | Ok => BrokerProtocol,
23 | Error => Closed,
24 | },
25 | }
26 |
27 | state_machine! {
28 | derive(Debug)
29 | AuthProtocolServer(Ready)
30 |
31 | Ready(ClientHelloReceived) => ClientHelloReceived,
32 | ClientHelloReceived(ServerHelloSent) => ServerHelloSent,
33 | ServerHelloSent(ClientAuthReceived) => ClientAuthReceived,
34 | ClientAuthReceived => {
35 | Ok => AuthResultOk,
36 | Error => AuthResultError,
37 | },
38 | AuthResultOk(AuthResultSent) => BrokerProtocol,
39 | AuthResultError(AuthResultSent) => Closed,
40 | }
41 |
42 | pub struct AuthProtocolHandler {
43 | machine: StateMachine,
44 | nonce: Option>,
45 | user: Option,
46 | }
47 |
48 | impl AuthProtocolHandler {
49 | pub fn new() -> AuthProtocolHandler {
50 | AuthProtocolHandler {
51 | machine: StateMachine::new(),
52 | nonce: None,
53 | user: None,
54 | }
55 | }
56 |
57 | pub fn get_user(&self) -> Option {
58 | self.user
59 | }
60 |
61 | pub fn handle_init(&mut self, client_hello: ClientHello) -> Result, ProtocolError> {
62 | let _ = self
63 | .machine
64 | .consume(&AuthProtocolServerInput::ClientHelloReceived)
65 | .map_err(|_e| ProtocolError::InvalidState)?;
66 |
67 | let mut random_buf = [0u8; 32];
68 | getrandom::getrandom(&mut random_buf).unwrap();
69 | let nonce = random_buf.to_vec();
70 | let reply = ServerHello::V0(ServerHelloV0 {
71 | nonce: nonce.clone(),
72 | });
73 | self.nonce = Some(nonce);
74 |
75 | let _ = self
76 | .machine
77 | .consume(&AuthProtocolServerInput::ServerHelloSent)
78 | .map_err(|_e| ProtocolError::InvalidState)?;
79 |
80 | //debug_println!("sending nonce to client: {:?}", self.nonce);
81 |
82 | Ok(serde_bare::to_vec(&reply).unwrap())
83 | }
84 |
85 | pub fn handle_incoming(
86 | &mut self,
87 | frame: Vec,
88 | ) -> (
89 | Result, ProtocolError>,
90 | Pin>>>,
91 | ) {
92 | fn prepare_reply(res: Result, ProtocolError>) -> AuthResult {
93 | let (result, metadata) = match res {
94 | Ok(m) => (0, m),
95 | Err(e) => (e.into(), vec![]),
96 | };
97 | AuthResult::V0(AuthResultV0 { result, metadata })
98 | }
99 |
100 | fn process_state(
101 | handler: &mut AuthProtocolHandler,
102 | frame: Vec,
103 | ) -> Result, ProtocolError> {
104 | match handler.machine.state() {
105 | &AuthProtocolServerState::ServerHelloSent => {
106 | let message = serde_bare::from_slice::(&frame)?;
107 | let _ = handler
108 | .machine
109 | .consume(&AuthProtocolServerInput::ClientAuthReceived)
110 | .map_err(|_e| ProtocolError::InvalidState)?;
111 |
112 | // verifying client auth
113 |
114 | debug_println!("verifying client auth");
115 |
116 | let _ = verify(
117 | &serde_bare::to_vec(&message.content_v0()).unwrap(),
118 | message.sig(),
119 | message.user(),
120 | )
121 | .map_err(|_e| ProtocolError::AccessDenied)?;
122 |
123 | // debug_println!(
124 | // "matching nonce : {:?} {:?}",
125 | // message.nonce(),
126 | // handler.nonce.as_ref().unwrap()
127 | // );
128 |
129 | if message.nonce() != handler.nonce.as_ref().unwrap() {
130 | let _ = handler
131 | .machine
132 | .consume(&AuthProtocolServerInput::Error)
133 | .map_err(|_e| ProtocolError::InvalidState);
134 |
135 | return Err(ProtocolError::AccessDenied);
136 | }
137 |
138 | // TODO check that the device has been registered for this user. if not, return AccessDenied
139 |
140 | // all is good, we advance the FSM and send back response
141 | let _ = handler
142 | .machine
143 | .consume(&AuthProtocolServerInput::Ok)
144 | .map_err(|_e| ProtocolError::InvalidState)?;
145 |
146 | handler.user = Some(message.user());
147 |
148 | Ok(vec![]) // without any metadata
149 | }
150 | _ => Err(ProtocolError::InvalidState),
151 | }
152 | }
153 |
154 | let res = process_state(self, frame);
155 | let is_err = res.as_ref().err().cloned();
156 | let reply = prepare_reply(res);
157 | let reply_ser: Result, ProtocolError> = Ok(serde_bare::to_vec(&reply).unwrap());
158 | if is_err.is_some() {
159 | (
160 | reply_ser,
161 | Box::pin(OptionFuture::from(Some(
162 | async move { reply.result() }.boxed(),
163 | ))),
164 | )
165 | } else {
166 | (reply_ser, Box::pin(OptionFuture::from(None)))
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/lofire-broker/src/config.rs:
--------------------------------------------------------------------------------
1 | //! Broker Config, persisted to store
2 |
3 | use lofire::brokerstore::BrokerStore;
4 | use lofire::store::*;
5 | use lofire::types::*;
6 | use lofire_net::types::*;
7 | use serde::{Deserialize, Serialize};
8 | use serde_bare::{from_slice, to_vec};
9 |
10 | // TODO: versioning V0
11 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
12 | pub enum ConfigMode {
13 | Local,
14 | Core,
15 | }
16 |
17 | pub struct Config<'a> {
18 | store: &'a dyn BrokerStore,
19 | }
20 |
21 | impl<'a> Config<'a> {
22 | const PREFIX: u8 = b"c"[0];
23 |
24 | const KEY: [u8; 5] = *b"onfig";
25 |
26 | // propertie's suffixes
27 | const MODE: u8 = b"m"[0];
28 |
29 | const ALL_PROPERTIES: [u8; 1] = [Self::MODE];
30 |
31 | const SUFFIX_FOR_EXIST_CHECK: u8 = Self::MODE;
32 |
33 | pub fn open(store: &'a dyn BrokerStore) -> Result, StorageError> {
34 | let opening = Config { store };
35 | if !opening.exists() {
36 | return Err(StorageError::NotFound);
37 | }
38 | Ok(opening)
39 | }
40 | pub fn get_or_create(
41 | mode: &ConfigMode,
42 | store: &'a dyn BrokerStore,
43 | ) -> Result, StorageError> {
44 | match Self::open(store) {
45 | Err(e) => {
46 | if e == StorageError::NotFound {
47 | Self::create(mode, store)
48 | } else {
49 | Err(StorageError::BackendError)
50 | }
51 | }
52 | Ok(p) => {
53 | if &p.mode().unwrap() != mode {
54 | return Err(StorageError::InvalidValue);
55 | }
56 | Ok(p)
57 | }
58 | }
59 | }
60 | pub fn create(
61 | mode: &ConfigMode,
62 | store: &'a dyn BrokerStore,
63 | ) -> Result, StorageError> {
64 | let acc = Config { store };
65 | if acc.exists() {
66 | return Err(StorageError::BackendError);
67 | }
68 | store.put(
69 | Self::PREFIX,
70 | &to_vec(&Self::KEY)?,
71 | Some(Self::MODE),
72 | to_vec(&mode)?,
73 | )?;
74 | Ok(acc)
75 | }
76 | pub fn exists(&self) -> bool {
77 | self.store
78 | .get(
79 | Self::PREFIX,
80 | &to_vec(&Self::KEY).unwrap(),
81 | Some(Self::SUFFIX_FOR_EXIST_CHECK),
82 | )
83 | .is_ok()
84 | }
85 | pub fn mode(&self) -> Result {
86 | match self
87 | .store
88 | .get(Self::PREFIX, &to_vec(&Self::KEY)?, Some(Self::MODE))
89 | {
90 | Ok(ver) => Ok(from_slice::(&ver)?),
91 | Err(e) => Err(e),
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lofire-broker/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[macro_export]
2 | macro_rules! before {
3 | ( $self:expr, $request_id:ident, $addr:ident, $receiver:ident ) => {
4 | let mut actor = BrokerMessageActor::new();
5 | let $receiver = actor.receiver();
6 | let mut $addr = actor
7 | .start()
8 | .await
9 | .map_err(|_e| ProtocolError::ActorError)?;
10 |
11 | let $request_id = $addr.actor_id();
12 | //debug_println!("actor ID {}", $request_id);
13 |
14 | {
15 | let mut map = $self.actors.write().expect("RwLock poisoned");
16 | map.insert($request_id, $addr.downgrade());
17 | }
18 | };
19 | }
20 |
21 | macro_rules! after {
22 | ( $self:expr, $request_id:ident, $addr:ident, $receiver:ident, $reply:ident ) => {
23 | //debug_println!("waiting for reply");
24 |
25 | $addr.wait_for_stop().await; // TODO add timeout and close connection if there's no reply
26 | let r = $receiver.await;
27 | if r.is_err() { return Err(ProtocolError::Closing);}
28 | let $reply = r.unwrap();
29 | //debug_println!("reply arrived {:?}", $reply);
30 | {
31 | let mut map = $self.actors.write().expect("RwLock poisoned");
32 | map.remove(&$request_id);
33 | }
34 | };
35 | }
36 |
37 | pub mod account;
38 |
39 | pub mod overlay;
40 |
41 | pub mod peer;
42 |
43 | pub mod topic;
44 |
45 | pub mod connection;
46 |
47 | pub mod server;
48 |
49 | pub mod config;
50 |
51 | pub mod repostoreinfo;
52 |
53 | pub mod auth;
54 |
--------------------------------------------------------------------------------
/lofire-broker/src/overlay.rs:
--------------------------------------------------------------------------------
1 | //! Overlay
2 |
3 | use lofire::brokerstore::BrokerStore;
4 | use lofire::store::*;
5 | use lofire::types::*;
6 | use lofire::utils::now_timestamp;
7 | use lofire_net::types::*;
8 | use serde::{Deserialize, Serialize};
9 | use serde_bare::{from_slice, to_vec};
10 |
11 | // TODO: versioning V0
12 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
13 | pub struct OverlayMeta {
14 | pub users: u32,
15 | pub last_used: Timestamp,
16 | }
17 |
18 | pub struct Overlay<'a> {
19 | /// Overlay ID
20 | id: OverlayId,
21 | store: &'a dyn BrokerStore,
22 | }
23 |
24 | impl<'a> Overlay<'a> {
25 | const PREFIX: u8 = b"o"[0];
26 |
27 | // propertie's suffixes
28 | const SECRET: u8 = b"s"[0];
29 | const PEER: u8 = b"p"[0];
30 | const TOPIC: u8 = b"t"[0];
31 | const META: u8 = b"m"[0];
32 | const REPO: u8 = b"r"[0];
33 |
34 | const ALL_PROPERTIES: [u8; 5] = [
35 | Self::SECRET,
36 | Self::PEER,
37 | Self::TOPIC,
38 | Self::META,
39 | Self::REPO,
40 | ];
41 |
42 | const SUFFIX_FOR_EXIST_CHECK: u8 = Self::SECRET;
43 |
44 | pub fn open(id: &OverlayId, store: &'a dyn BrokerStore) -> Result, StorageError> {
45 | let opening = Overlay {
46 | id: id.clone(),
47 | store,
48 | };
49 | if !opening.exists() {
50 | return Err(StorageError::NotFound);
51 | }
52 | Ok(opening)
53 | }
54 | pub fn create(
55 | id: &OverlayId,
56 | secret: &SymKey,
57 | repo: Option,
58 | store: &'a dyn BrokerStore,
59 | ) -> Result, StorageError> {
60 | let acc = Overlay {
61 | id: id.clone(),
62 | store,
63 | };
64 | if acc.exists() {
65 | return Err(StorageError::BackendError);
66 | }
67 | store.put(
68 | Self::PREFIX,
69 | &to_vec(&id)?,
70 | Some(Self::SECRET),
71 | to_vec(&secret)?,
72 | )?;
73 | if repo.is_some() {
74 | store.put(
75 | Self::PREFIX,
76 | &to_vec(&id)?,
77 | Some(Self::REPO),
78 | to_vec(&repo.unwrap())?,
79 | )?;
80 | //TODO if failure, should remove the previously added SECRET property
81 | }
82 | let meta = OverlayMeta {
83 | users: 1,
84 | last_used: now_timestamp(),
85 | };
86 | store.put(
87 | Self::PREFIX,
88 | &to_vec(&id)?,
89 | Some(Self::META),
90 | to_vec(&meta)?,
91 | )?;
92 | //TODO if failure, should remove the previously added SECRET and REPO properties
93 | Ok(acc)
94 | }
95 | pub fn exists(&self) -> bool {
96 | self.store
97 | .get(
98 | Self::PREFIX,
99 | &to_vec(&self.id).unwrap(),
100 | Some(Self::SUFFIX_FOR_EXIST_CHECK),
101 | )
102 | .is_ok()
103 | }
104 | pub fn id(&self) -> OverlayId {
105 | self.id
106 | }
107 | pub fn add_peer(&self, peer: &PeerId) -> Result<(), StorageError> {
108 | if !self.exists() {
109 | return Err(StorageError::BackendError);
110 | }
111 | self.store.put(
112 | Self::PREFIX,
113 | &to_vec(&self.id)?,
114 | Some(Self::PEER),
115 | to_vec(peer)?,
116 | )
117 | }
118 | pub fn remove_peer(&self, peer: &PeerId) -> Result<(), StorageError> {
119 | self.store.del_property_value(
120 | Self::PREFIX,
121 | &to_vec(&self.id)?,
122 | Some(Self::PEER),
123 | to_vec(peer)?,
124 | )
125 | }
126 |
127 | pub fn has_peer(&self, peer: &PeerId) -> Result<(), StorageError> {
128 | self.store.has_property_value(
129 | Self::PREFIX,
130 | &to_vec(&self.id)?,
131 | Some(Self::PEER),
132 | to_vec(peer)?,
133 | )
134 | }
135 |
136 | pub fn add_topic(&self, topic: &TopicId) -> Result<(), StorageError> {
137 | if !self.exists() {
138 | return Err(StorageError::BackendError);
139 | }
140 | self.store.put(
141 | Self::PREFIX,
142 | &to_vec(&self.id)?,
143 | Some(Self::TOPIC),
144 | to_vec(topic)?,
145 | )
146 | }
147 | pub fn remove_topic(&self, topic: &TopicId) -> Result<(), StorageError> {
148 | self.store.del_property_value(
149 | Self::PREFIX,
150 | &to_vec(&self.id)?,
151 | Some(Self::TOPIC),
152 | to_vec(topic)?,
153 | )
154 | }
155 |
156 | pub fn has_topic(&self, topic: &TopicId) -> Result<(), StorageError> {
157 | self.store.has_property_value(
158 | Self::PREFIX,
159 | &to_vec(&self.id)?,
160 | Some(Self::TOPIC),
161 | to_vec(topic)?,
162 | )
163 | }
164 |
165 | pub fn secret(&self) -> Result {
166 | match self
167 | .store
168 | .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::SECRET))
169 | {
170 | Ok(secret) => Ok(from_slice::(&secret)?),
171 | Err(e) => Err(e),
172 | }
173 | }
174 |
175 | pub fn metadata(&self) -> Result {
176 | match self
177 | .store
178 | .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::META))
179 | {
180 | Ok(meta) => Ok(from_slice::(&meta)?),
181 | Err(e) => Err(e),
182 | }
183 | }
184 | pub fn set_metadata(&self, meta: &OverlayMeta) -> Result<(), StorageError> {
185 | if !self.exists() {
186 | return Err(StorageError::BackendError);
187 | }
188 | self.store.replace(
189 | Self::PREFIX,
190 | &to_vec(&self.id)?,
191 | Some(Self::META),
192 | to_vec(meta)?,
193 | )
194 | }
195 |
196 | pub fn repo(&self) -> Result {
197 | match self
198 | .store
199 | .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::REPO))
200 | {
201 | Ok(repo) => Ok(from_slice::(&repo)?),
202 | Err(e) => Err(e),
203 | }
204 | }
205 |
206 | pub fn del(&self) -> Result<(), StorageError> {
207 | self.store
208 | .del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/lofire-broker/src/peer.rs:
--------------------------------------------------------------------------------
1 | //! Peer
2 |
3 | use lofire::brokerstore::BrokerStore;
4 | use lofire::store::*;
5 | use lofire::types::*;
6 | use lofire_net::types::*;
7 | use serde::{Deserialize, Serialize};
8 | use serde_bare::{from_slice, to_vec};
9 |
10 | pub struct Peer<'a> {
11 | /// Topic ID
12 | id: PeerId,
13 | store: &'a dyn BrokerStore,
14 | }
15 |
16 | impl<'a> Peer<'a> {
17 | const PREFIX: u8 = b"p"[0];
18 |
19 | // propertie's suffixes
20 | const VERSION: u8 = b"v"[0];
21 | const ADVERT: u8 = b"a"[0];
22 |
23 | const ALL_PROPERTIES: [u8; 2] = [Self::VERSION, Self::ADVERT];
24 |
25 | const SUFFIX_FOR_EXIST_CHECK: u8 = Self::VERSION;
26 |
27 | pub fn open(id: &PeerId, store: &'a dyn BrokerStore) -> Result, StorageError> {
28 | let opening = Peer {
29 | id: id.clone(),
30 | store,
31 | };
32 | if !opening.exists() {
33 | return Err(StorageError::NotFound);
34 | }
35 | Ok(opening)
36 | }
37 | pub fn update_or_create(
38 | advert: &PeerAdvert,
39 | store: &'a dyn BrokerStore,
40 | ) -> Result, StorageError> {
41 | let id = advert.peer();
42 | match Self::open(id, store) {
43 | Err(e) => {
44 | if e == StorageError::NotFound {
45 | Self::create(advert, store)
46 | } else {
47 | Err(StorageError::BackendError)
48 | }
49 | }
50 | Ok(p) => {
51 | p.update_advert(advert)?;
52 | Ok(p)
53 | }
54 | }
55 | }
56 | pub fn create(
57 | advert: &PeerAdvert,
58 | store: &'a dyn BrokerStore,
59 | ) -> Result, StorageError> {
60 | let id = advert.peer();
61 | let acc = Peer {
62 | id: id.clone(),
63 | store,
64 | };
65 | if acc.exists() {
66 | return Err(StorageError::BackendError);
67 | }
68 | store.put(
69 | Self::PREFIX,
70 | &to_vec(&id)?,
71 | Some(Self::VERSION),
72 | to_vec(&advert.version())?,
73 | )?;
74 | store.put(
75 | Self::PREFIX,
76 | &to_vec(&id)?,
77 | Some(Self::ADVERT),
78 | to_vec(&advert)?,
79 | )?;
80 | Ok(acc)
81 | }
82 | pub fn exists(&self) -> bool {
83 | self.store
84 | .get(
85 | Self::PREFIX,
86 | &to_vec(&self.id).unwrap(),
87 | Some(Self::SUFFIX_FOR_EXIST_CHECK),
88 | )
89 | .is_ok()
90 | }
91 | pub fn id(&self) -> PeerId {
92 | self.id
93 | }
94 | pub fn version(&self) -> Result {
95 | match self
96 | .store
97 | .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::VERSION))
98 | {
99 | Ok(ver) => Ok(from_slice::(&ver)?),
100 | Err(e) => Err(e),
101 | }
102 | }
103 | pub fn set_version(&self, version: u32) -> Result<(), StorageError> {
104 | if !self.exists() {
105 | return Err(StorageError::BackendError);
106 | }
107 | self.store.replace(
108 | Self::PREFIX,
109 | &to_vec(&self.id)?,
110 | Some(Self::VERSION),
111 | to_vec(&version)?,
112 | )
113 | }
114 | pub fn update_advert(&self, advert: &PeerAdvert) -> Result<(), StorageError> {
115 | if advert.peer() != &self.id {
116 | return Err(StorageError::InvalidValue);
117 | }
118 | let current_advert = self.advert().map_err(|e| StorageError::BackendError)?;
119 | if current_advert.version() >= advert.version() {
120 | return Ok(());
121 | }
122 | self.store.replace(
123 | Self::PREFIX,
124 | &to_vec(&self.id)?,
125 | Some(Self::ADVERT),
126 | to_vec(advert)?,
127 | )
128 | }
129 | pub fn advert(&self) -> Result {
130 | match self
131 | .store
132 | .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::ADVERT))
133 | {
134 | Ok(advert) => Ok(from_slice::(&advert)?),
135 | Err(e) => Err(e),
136 | }
137 | }
138 | pub fn set_advert(&self, advert: &PeerAdvert) -> Result<(), StorageError> {
139 | if !self.exists() {
140 | return Err(StorageError::BackendError);
141 | }
142 | self.store.replace(
143 | Self::PREFIX,
144 | &to_vec(&self.id)?,
145 | Some(Self::ADVERT),
146 | to_vec(advert)?,
147 | )
148 | }
149 |
150 | pub fn del(&self) -> Result<(), StorageError> {
151 | self.store
152 | .del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/lofire-broker/src/repostoreinfo.rs:
--------------------------------------------------------------------------------
1 | //! RepoStore information about each RepoStore
2 | //! It contains the symKeys to open the RepoStores
3 | //! A repoStore is identified by its repo pubkey if in local mode
4 | //! In core mode, it is identified by the overlayid.
5 |
6 | use lofire::brokerstore::BrokerStore;
7 | use lofire::store::*;
8 | use lofire::types::*;
9 | use lofire_net::types::*;
10 | use serde::{Deserialize, Serialize};
11 | use serde_bare::{from_slice, to_vec};
12 |
13 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
14 | pub enum RepoStoreId {
15 | Overlay(OverlayId),
16 | Repo(PubKey),
17 | }
18 |
19 | impl From for String {
20 | fn from(id: RepoStoreId) -> Self {
21 | hex::encode(to_vec(&id).unwrap())
22 | }
23 | }
24 |
25 | pub struct RepoStoreInfo<'a> {
26 | /// RepoStore ID
27 | id: RepoStoreId,
28 | store: &'a dyn BrokerStore,
29 | }
30 |
31 | impl<'a> RepoStoreInfo<'a> {
32 | const PREFIX: u8 = b"r"[0];
33 |
34 | // propertie's suffixes
35 | const KEY: u8 = b"k"[0];
36 |
37 | const ALL_PROPERTIES: [u8; 1] = [Self::KEY];
38 |
39 | const SUFFIX_FOR_EXIST_CHECK: u8 = Self::KEY;
40 |
41 | pub fn open(
42 | id: &RepoStoreId,
43 | store: &'a dyn BrokerStore,
44 | ) -> Result, StorageError> {
45 | let opening = RepoStoreInfo {
46 | id: id.clone(),
47 | store,
48 | };
49 | if !opening.exists() {
50 | return Err(StorageError::NotFound);
51 | }
52 | Ok(opening)
53 | }
54 | pub fn create(
55 | id: &RepoStoreId,
56 | key: &SymKey,
57 | store: &'a dyn BrokerStore,
58 | ) -> Result, StorageError> {
59 | let acc = RepoStoreInfo {
60 | id: id.clone(),
61 | store,
62 | };
63 | if acc.exists() {
64 | return Err(StorageError::BackendError);
65 | }
66 | store.put(Self::PREFIX, &to_vec(&id)?, Some(Self::KEY), to_vec(key)?)?;
67 | Ok(acc)
68 | }
69 | pub fn exists(&self) -> bool {
70 | self.store
71 | .get(
72 | Self::PREFIX,
73 | &to_vec(&self.id).unwrap(),
74 | Some(Self::SUFFIX_FOR_EXIST_CHECK),
75 | )
76 | .is_ok()
77 | }
78 | pub fn id(&self) -> &RepoStoreId {
79 | &self.id
80 | }
81 | pub fn key(&self) -> Result {
82 | match self
83 | .store
84 | .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::KEY))
85 | {
86 | Ok(k) => Ok(from_slice::(&k)?),
87 | Err(e) => Err(e),
88 | }
89 | }
90 | pub fn del(&self) -> Result<(), StorageError> {
91 | self.store
92 | .del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lofire-broker/src/topic.rs:
--------------------------------------------------------------------------------
1 | //! Topic
2 |
3 | use lofire::brokerstore::BrokerStore;
4 | use lofire::store::*;
5 | use lofire::types::*;
6 | use lofire_net::types::*;
7 | use serde::{Deserialize, Serialize};
8 | use serde_bare::{from_slice, to_vec};
9 |
10 | // TODO: versioning V0
11 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
12 | pub struct TopicMeta {
13 | pub users: u32,
14 | }
15 |
16 | pub struct Topic<'a> {
17 | /// Topic ID
18 | id: TopicId,
19 | store: &'a dyn BrokerStore,
20 | }
21 |
22 | impl<'a> Topic<'a> {
23 | const PREFIX: u8 = b"t"[0];
24 |
25 | // propertie's suffixes
26 | const ADVERT: u8 = b"a"[0];
27 | const HEAD: u8 = b"h"[0];
28 | const META: u8 = b"m"[0];
29 |
30 | const ALL_PROPERTIES: [u8; 3] = [Self::ADVERT, Self::HEAD, Self::META];
31 |
32 | const SUFFIX_FOR_EXIST_CHECK: u8 = Self::META;
33 |
34 | pub fn open(id: &TopicId, store: &'a dyn BrokerStore) -> Result, StorageError> {
35 | let opening = Topic {
36 | id: id.clone(),
37 | store,
38 | };
39 | if !opening.exists() {
40 | return Err(StorageError::NotFound);
41 | }
42 | Ok(opening)
43 | }
44 | pub fn create(id: &TopicId, store: &'a dyn BrokerStore) -> Result, StorageError> {
45 | let acc = Topic {
46 | id: id.clone(),
47 | store,
48 | };
49 | if acc.exists() {
50 | return Err(StorageError::BackendError);
51 | }
52 | let meta = TopicMeta { users: 0 };
53 | store.put(
54 | Self::PREFIX,
55 | &to_vec(&id)?,
56 | Some(Self::META),
57 | to_vec(&meta)?,
58 | )?;
59 | Ok(acc)
60 | }
61 | pub fn exists(&self) -> bool {
62 | self.store
63 | .get(
64 | Self::PREFIX,
65 | &to_vec(&self.id).unwrap(),
66 | Some(Self::SUFFIX_FOR_EXIST_CHECK),
67 | )
68 | .is_ok()
69 | }
70 | pub fn id(&self) -> TopicId {
71 | self.id
72 | }
73 | pub fn add_head(&self, head: &ObjectId) -> Result<(), StorageError> {
74 | if !self.exists() {
75 | return Err(StorageError::BackendError);
76 | }
77 | self.store.put(
78 | Self::PREFIX,
79 | &to_vec(&self.id)?,
80 | Some(Self::HEAD),
81 | to_vec(head)?,
82 | )
83 | }
84 | pub fn remove_head(&self, head: &ObjectId) -> Result<(), StorageError> {
85 | self.store.del_property_value(
86 | Self::PREFIX,
87 | &to_vec(&self.id)?,
88 | Some(Self::HEAD),
89 | to_vec(head)?,
90 | )
91 | }
92 |
93 | pub fn has_head(&self, head: &ObjectId) -> Result<(), StorageError> {
94 | self.store.has_property_value(
95 | Self::PREFIX,
96 | &to_vec(&self.id)?,
97 | Some(Self::HEAD),
98 | to_vec(head)?,
99 | )
100 | }
101 |
102 | pub fn metadata(&self) -> Result {
103 | match self
104 | .store
105 | .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::META))
106 | {
107 | Ok(meta) => Ok(from_slice::(&meta)?),
108 | Err(e) => Err(e),
109 | }
110 | }
111 | pub fn set_metadata(&self, meta: &TopicMeta) -> Result<(), StorageError> {
112 | if !self.exists() {
113 | return Err(StorageError::BackendError);
114 | }
115 | self.store.replace(
116 | Self::PREFIX,
117 | &to_vec(&self.id)?,
118 | Some(Self::META),
119 | to_vec(meta)?,
120 | )
121 | }
122 |
123 | pub fn del(&self) -> Result<(), StorageError> {
124 | self.store
125 | .del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/lofire-demo/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lofire-demo"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | debug_print = "1.0.0"
10 | lofire = { path = "../lofire" }
11 | lofire-net = { path = "../lofire-net" }
12 | lofire-broker = { path = "../lofire-broker" }
13 | lofire-store-lmdb = { path = "../lofire-store-lmdb" }
14 | async-std = { version = "1.7.0", features = ["attributes"] }
15 | async-tungstenite = { version = "0.17.2", features = ["async-std-runtime","async-native-tls"] }
16 | futures = "0.3.24"
17 | xactor = "0.7.11"
18 | tempfile = "3"
19 | fastbloom-rs = "0.3.1"
20 | rand = "0.7"
21 | ed25519-dalek = "1.0.1"
22 | assert_cmd = "2.0.5"
23 |
--------------------------------------------------------------------------------
/lofire-demo/src/main.rs:
--------------------------------------------------------------------------------
1 | use async_tungstenite::async_std::connect_async;
2 | use async_tungstenite::client_async;
3 | use async_tungstenite::tungstenite::{Error, Message};
4 | use debug_print::*;
5 | use ed25519_dalek::*;
6 | use fastbloom_rs::{BloomFilter as Filter, FilterBuilder, Membership};
7 | use futures::{future, pin_mut, stream, SinkExt, StreamExt};
8 | use lofire::object::Object;
9 | use lofire::store::{store_max_value_size, store_valid_value_size, HashMapRepoStore, RepoStore};
10 | use lofire_broker::config::ConfigMode;
11 | use lofire_store_lmdb::brokerstore::LmdbBrokerStore;
12 | use lofire_store_lmdb::repostore::LmdbRepoStore;
13 | use rand::rngs::OsRng;
14 | use std::collections::HashMap;
15 |
16 | use lofire::types::*;
17 | use lofire::utils::{generate_keypair, now_timestamp};
18 | use lofire_broker::connection::*;
19 | use lofire_broker::server::*;
20 | use lofire_net::errors::*;
21 | use lofire_net::types::*;
22 |
23 | fn block_size() -> usize {
24 | store_max_value_size()
25 | //store_valid_value_size(0)
26 | }
27 |
28 | async fn test_sync(cnx: &mut impl BrokerConnection, user_pub_key: PubKey, userpriv_key: PrivKey) {
29 | fn add_obj(
30 | content: ObjectContent,
31 | deps: Vec,
32 | expiry: Option,
33 | repo_pubkey: PubKey,
34 | repo_secret: SymKey,
35 | store: &mut impl RepoStore,
36 | ) -> ObjectRef {
37 | let max_object_size = 4000;
38 | let obj = Object::new(
39 | content,
40 | deps,
41 | expiry,
42 | max_object_size,
43 | repo_pubkey,
44 | repo_secret,
45 | );
46 | //println!(">>> add_obj");
47 | println!(" id: {}", obj.id());
48 | //println!(" deps: {:?}", obj.deps());
49 | obj.save(store).unwrap();
50 | obj.reference().unwrap()
51 | }
52 |
53 | fn add_commit(
54 | branch: ObjectRef,
55 | author_privkey: PrivKey,
56 | author_pubkey: PubKey,
57 | seq: u32,
58 | deps: Vec,
59 | acks: Vec,
60 | body_ref: ObjectRef,
61 | repo_pubkey: PubKey,
62 | repo_secret: SymKey,
63 | store: &mut impl RepoStore,
64 | ) -> ObjectRef {
65 | let mut obj_deps: Vec = vec![];
66 | obj_deps.extend(deps.iter().map(|r| r.id));
67 | obj_deps.extend(acks.iter().map(|r| r.id));
68 |
69 | let obj_ref = ObjectRef {
70 | id: ObjectId::Blake3Digest32([1; 32]),
71 | key: SymKey::ChaCha20Key([2; 32]),
72 | };
73 | let refs = vec![obj_ref];
74 | let metadata = vec![5u8; 55];
75 | let expiry = None;
76 |
77 | let commit = Commit::new(
78 | author_privkey,
79 | author_pubkey,
80 | seq,
81 | branch,
82 | deps,
83 | acks,
84 | refs,
85 | metadata,
86 | body_ref,
87 | expiry,
88 | )
89 | .unwrap();
90 | //println!("commit: {}", commit.id().unwrap());
91 | add_obj(
92 | ObjectContent::Commit(commit),
93 | obj_deps,
94 | expiry,
95 | repo_pubkey,
96 | repo_secret,
97 | store,
98 | )
99 | }
100 |
101 | fn add_body_branch(
102 | branch: Branch,
103 | repo_pubkey: PubKey,
104 | repo_secret: SymKey,
105 | store: &mut impl RepoStore,
106 | ) -> ObjectRef {
107 | let deps = vec![];
108 | let expiry = None;
109 | let body = CommitBody::Branch(branch);
110 | //println!("body: {:?}", body);
111 | add_obj(
112 | ObjectContent::CommitBody(body),
113 | deps,
114 | expiry,
115 | repo_pubkey,
116 | repo_secret,
117 | store,
118 | )
119 | }
120 |
121 | fn add_body_trans(
122 | deps: Vec,
123 | repo_pubkey: PubKey,
124 | repo_secret: SymKey,
125 | store: &mut impl RepoStore,
126 | ) -> ObjectRef {
127 | let expiry = None;
128 | let content = [7u8; 777].to_vec();
129 | let body = CommitBody::Transaction(Transaction::V0(content));
130 | //println!("body: {:?}", body);
131 | add_obj(
132 | ObjectContent::CommitBody(body),
133 | deps,
134 | expiry,
135 | repo_pubkey,
136 | repo_secret,
137 | store,
138 | )
139 | }
140 |
141 | fn add_body_ack(
142 | deps: Vec,
143 | repo_pubkey: PubKey,
144 | repo_secret: SymKey,
145 | store: &mut impl RepoStore,
146 | ) -> ObjectRef {
147 | let expiry = None;
148 | let body = CommitBody::Ack(Ack::V0());
149 | //println!("body: {:?}", body);
150 | add_obj(
151 | ObjectContent::CommitBody(body),
152 | deps,
153 | expiry,
154 | repo_pubkey,
155 | repo_secret,
156 | store,
157 | )
158 | }
159 |
160 | let mut store = HashMapRepoStore::new();
161 | let mut rng = OsRng {};
162 |
163 | // repo
164 |
165 | let repo_keypair: Keypair = Keypair::generate(&mut rng);
166 | // println!(
167 | // "repo private key: ({}) {:?}",
168 | // repo_keypair.secret.as_bytes().len(),
169 | // repo_keypair.secret.as_bytes()
170 | // );
171 | // println!(
172 | // "repo public key: ({}) {:?}",
173 | // repo_keypair.public.as_bytes().len(),
174 | // repo_keypair.public.as_bytes()
175 | // );
176 | let _repo_privkey = PrivKey::Ed25519PrivKey(repo_keypair.secret.to_bytes());
177 | let repo_pubkey = PubKey::Ed25519PubKey(repo_keypair.public.to_bytes());
178 | let repo_secret = SymKey::ChaCha20Key([9; 32]);
179 |
180 | let repolink = RepoLink::V0(RepoLinkV0 {
181 | id: repo_pubkey,
182 | secret: repo_secret,
183 | peers: vec![],
184 | });
185 |
186 | // branch
187 |
188 | let branch_keypair: Keypair = Keypair::generate(&mut rng);
189 | //println!("branch public key: {:?}", branch_keypair.public.as_bytes());
190 | let branch_pubkey = PubKey::Ed25519PubKey(branch_keypair.public.to_bytes());
191 |
192 | let member_keypair: Keypair = Keypair::generate(&mut rng);
193 | //println!("member public key: {:?}", member_keypair.public.as_bytes());
194 | let member_privkey = PrivKey::Ed25519PrivKey(member_keypair.secret.to_bytes());
195 | let member_pubkey = PubKey::Ed25519PubKey(member_keypair.public.to_bytes());
196 |
197 | let metadata = [66u8; 64].to_vec();
198 | let commit_types = vec![CommitType::Ack, CommitType::Transaction];
199 | let secret = SymKey::ChaCha20Key([0; 32]);
200 |
201 | let member = MemberV0::new(member_pubkey, commit_types, metadata.clone());
202 | let members = vec![member];
203 | let mut quorum = HashMap::new();
204 | quorum.insert(CommitType::Transaction, 3);
205 | let ack_delay = RelTime::Minutes(3);
206 | let tags = [99u8; 32].to_vec();
207 | let branch = Branch::new(
208 | branch_pubkey,
209 | branch_pubkey,
210 | secret,
211 | members,
212 | quorum,
213 | ack_delay,
214 | tags,
215 | metadata,
216 | );
217 | //println!("branch: {:?}", branch);
218 |
219 | println!("branch deps/acks:");
220 | println!("");
221 | println!(" br");
222 | println!(" / \\");
223 | println!(" t1 t2");
224 | println!(" / \\ / \\");
225 | println!(" a3 t4<--t5-->(t1)");
226 | println!(" / \\");
227 | println!(" a6 a7");
228 | println!("");
229 |
230 | // commit bodies
231 |
232 | let branch_body = add_body_branch(
233 | branch.clone(),
234 | repo_pubkey.clone(),
235 | repo_secret.clone(),
236 | &mut store,
237 | );
238 | let ack_body = add_body_ack(vec![], repo_pubkey, repo_secret, &mut store);
239 | let trans_body = add_body_trans(vec![], repo_pubkey, repo_secret, &mut store);
240 |
241 | // create & add commits to store
242 |
243 | println!(">> br");
244 | let br = add_commit(
245 | branch_body,
246 | member_privkey,
247 | member_pubkey,
248 | 0,
249 | vec![],
250 | vec![],
251 | branch_body,
252 | repo_pubkey,
253 | repo_secret,
254 | &mut store,
255 | );
256 |
257 | println!(">> t1");
258 | let t1 = add_commit(
259 | branch_body,
260 | member_privkey,
261 | member_pubkey,
262 | 1,
263 | vec![br],
264 | vec![],
265 | trans_body,
266 | repo_pubkey,
267 | repo_secret,
268 | &mut store,
269 | );
270 |
271 | println!(">> t2");
272 | let t2 = add_commit(
273 | branch_body,
274 | member_privkey,
275 | member_pubkey,
276 | 2,
277 | vec![br],
278 | vec![],
279 | trans_body,
280 | repo_pubkey,
281 | repo_secret,
282 | &mut store,
283 | );
284 |
285 | println!(">> a3");
286 | let a3 = add_commit(
287 | branch_body,
288 | member_privkey,
289 | member_pubkey,
290 | 3,
291 | vec![t1],
292 | vec![],
293 | ack_body,
294 | repo_pubkey,
295 | repo_secret,
296 | &mut store,
297 | );
298 |
299 | println!(">> t4");
300 | let t4 = add_commit(
301 | branch_body,
302 | member_privkey,
303 | member_pubkey,
304 | 4,
305 | vec![t2],
306 | vec![t1],
307 | trans_body,
308 | repo_pubkey,
309 | repo_secret,
310 | &mut store,
311 | );
312 |
313 | println!(">> t5");
314 | let t5 = add_commit(
315 | branch_body,
316 | member_privkey,
317 | member_pubkey,
318 | 5,
319 | vec![t1, t2],
320 | vec![t4],
321 | trans_body,
322 | repo_pubkey,
323 | repo_secret,
324 | &mut store,
325 | );
326 |
327 | println!(">> a6");
328 | let a6 = add_commit(
329 | branch_body,
330 | member_privkey,
331 | member_pubkey,
332 | 6,
333 | vec![t4],
334 | vec![],
335 | ack_body,
336 | repo_pubkey,
337 | repo_secret,
338 | &mut store,
339 | );
340 |
341 | println!(">> a7");
342 | let a7 = add_commit(
343 | branch_body,
344 | member_privkey,
345 | member_pubkey,
346 | 7,
347 | vec![t4],
348 | vec![],
349 | ack_body,
350 | repo_pubkey,
351 | repo_secret,
352 | &mut store,
353 | );
354 |
355 | let mut public_overlay_cnx = cnx
356 | .overlay_connect(&repolink, true)
357 | .await
358 | .expect("overlay_connect failed");
359 |
360 | // Sending everything to the broker
361 | for (v) in store.get_all() {
362 | //debug_println!("SENDING {}", k);
363 | let _ = public_overlay_cnx
364 | .put_block(&v)
365 | .await
366 | .expect("put_block failed");
367 | }
368 |
369 | // Now emptying the local store of the client, and adding only 1 commit into it (br)
370 | // we also have received an commit (t5) but we don't know what to do with it...
371 | let mut store = HashMapRepoStore::new();
372 |
373 | let br = add_commit(
374 | branch_body,
375 | member_privkey,
376 | member_pubkey,
377 | 0,
378 | vec![],
379 | vec![],
380 | branch_body,
381 | repo_pubkey,
382 | repo_secret,
383 | &mut store,
384 | );
385 |
386 | let t5 = add_commit(
387 | branch_body,
388 | member_privkey,
389 | member_pubkey,
390 | 5,
391 | vec![t1, t2],
392 | vec![t4],
393 | trans_body,
394 | repo_pubkey,
395 | repo_secret,
396 | &mut store,
397 | );
398 |
399 | debug_println!("LOCAL STORE HAS {} BLOCKS", store.get_len());
400 |
401 | // Let's pretend that we know that the head of the branch in the broker is at commits a6 and a7.
402 | // normally it would be the pub/sub that notifies us of those heads.
403 | // now we want to synchronize with the broker.
404 |
405 | let mut filter = Filter::new(FilterBuilder::new(10, 0.01));
406 | for commit_ref in [br, t5] {
407 | match commit_ref.id {
408 | ObjectId::Blake3Digest32(d) => filter.add(&d),
409 | }
410 | }
411 | let cfg = filter.config();
412 |
413 | let known_commits = BloomFilter {
414 | k: cfg.hashes,
415 | f: filter.get_u8_array().to_vec(),
416 | };
417 |
418 | let known_heads = [br.id];
419 |
420 | let remote_heads = [a6.id, a7.id];
421 |
422 | let mut synced_blocks_stream = public_overlay_cnx
423 | .sync_branch(remote_heads.to_vec(), known_heads.to_vec(), known_commits)
424 | .await
425 | .expect("sync_branch failed");
426 |
427 | let mut i = 0;
428 | while let Some(b) = synced_blocks_stream.next().await {
429 | debug_println!("GOT BLOCK {}", b.id());
430 | store.put(&b);
431 | i += 1;
432 | }
433 |
434 | debug_println!("SYNCED {} BLOCKS", i);
435 |
436 | debug_println!("LOCAL STORE HAS {} BLOCKS", store.get_len());
437 |
438 | // now the client can verify the DAG and each commit. Then update its list of heads.
439 | }
440 |
441 | async fn test(cnx: &mut impl BrokerConnection, pub_key: PubKey, priv_key: PrivKey) -> Result<(), ProtocolError>{
442 |
443 | cnx.add_user(PubKey::Ed25519PubKey([1; 32]), priv_key).await?;
444 |
445 | cnx.add_user(pub_key, priv_key).await?;
446 | //.expect("add_user 2 (myself) failed");
447 |
448 | assert_eq!(
449 | cnx.add_user(PubKey::Ed25519PubKey([1; 32]), priv_key).await.err().unwrap(),
450 | ProtocolError::UserAlreadyExists
451 | );
452 |
453 | let repo = RepoLink::V0(RepoLinkV0 {
454 | id: PubKey::Ed25519PubKey([1; 32]),
455 | secret: SymKey::ChaCha20Key([0; 32]),
456 | peers: vec![],
457 | });
458 | let mut public_overlay_cnx = cnx
459 | .overlay_connect(&repo, true)
460 | .await?;
461 |
462 | let my_block_id = public_overlay_cnx
463 | .put_block(&Block::new(
464 | vec![],
465 | ObjectDeps::ObjectIdList(vec![]),
466 | None,
467 | vec![27; 150],
468 | None,
469 | ))
470 | .await?;
471 |
472 | debug_println!("added block_id to store {}", my_block_id);
473 |
474 | let object_id = public_overlay_cnx
475 | .put_object(
476 | ObjectContent::File(File::V0(FileV0 {
477 | content_type: vec![],
478 | metadata: vec![],
479 | content: vec![48; 69000],
480 | })),
481 | vec![],
482 | None,
483 | block_size(),
484 | repo.id(),
485 | repo.secret(),
486 | )
487 | .await?;
488 |
489 | debug_println!("added object_id to store {}", object_id);
490 |
491 | let mut my_block_stream = public_overlay_cnx
492 | .get_block(my_block_id, true, None)
493 | .await?;
494 | //.expect("get_block failed");
495 |
496 | while let Some(b) = my_block_stream.next().await {
497 | debug_println!("GOT BLOCK {}", b.id());
498 | }
499 |
500 | let mut my_object_stream = public_overlay_cnx
501 | .get_block(object_id, true, None)
502 | .await?;
503 | //.expect("get_block for object failed");
504 |
505 | while let Some(b) = my_object_stream.next().await {
506 | debug_println!("GOT BLOCK {}", b.id());
507 | }
508 |
509 | let object = public_overlay_cnx
510 | .get_object(object_id, None)
511 | .await?;
512 | //.expect("get_object failed");
513 |
514 | debug_println!("GOT OBJECT with ID {}", object.id());
515 |
516 | // let object_id = public_overlay_cnx
517 | // .copy_object(object_id, Some(now_timestamp() + 60))
518 | // .await
519 | // .expect("copy_object failed");
520 |
521 | // debug_println!("COPIED OBJECT to OBJECT ID {}", object_id);
522 |
523 | public_overlay_cnx
524 | .delete_object(object_id)
525 | .await?;
526 | //.expect("delete_object failed");
527 |
528 | let res = public_overlay_cnx
529 | .get_object(object_id, None)
530 | .await
531 | .unwrap_err();
532 |
533 | debug_println!("result from get object after delete: {}", res);
534 | assert_eq!(res, ProtocolError::NotFound);
535 |
536 | //TODO test pin/unpin
537 |
538 | // TEST BRANCH SYNC
539 |
540 | test_sync(cnx, pub_key, priv_key).await;
541 |
542 | Ok(())
543 | }
544 |
545 | async fn test_local_connection() {
546 | debug_println!("===== TESTING LOCAL API =====");
547 |
548 | let root = tempfile::Builder::new()
549 | .prefix("node-daemon")
550 | .tempdir()
551 | .unwrap();
552 | let master_key: [u8; 32] = [0; 32];
553 | std::fs::create_dir_all(root.path()).unwrap();
554 | println!("{}", root.path().to_str().unwrap());
555 | let store = LmdbBrokerStore::open(root.path(), master_key);
556 |
557 | let mut server = BrokerServer::new(store, ConfigMode::Local).expect("starting broker");
558 |
559 | let (priv_key, pub_key) = generate_keypair();
560 |
561 | let mut cnx = server.local_connection(pub_key);
562 |
563 | test(&mut cnx, pub_key, priv_key).await;
564 | }
565 |
566 | async fn test_remote_connection() {
567 | debug_println!("===== TESTING REMOTE API =====");
568 |
569 | let res = connect_async("ws://127.0.0.1:3012").await;
570 |
571 | match (res) {
572 | Ok((ws, _)) => {
573 | debug_println!("WebSocket handshake completed");
574 |
575 | let (write, read) = ws.split();
576 | let mut frames_stream_read = read.map(|msg_res| match msg_res {
577 | Err(e) => {
578 | debug_println!("ERROR {:?}", e);
579 | vec![]
580 | }
581 | Ok(message) => {
582 | if message.is_close() {
583 | debug_println!("CLOSE FROM SERVER");
584 | vec![]
585 | } else {
586 | message.into_data()
587 | }
588 | }
589 | });
590 | async fn transform(message: Vec) -> Result {
591 | if message.len() == 0 {
592 | debug_println!("sending CLOSE message to SERVER");
593 | Ok(Message::Close(None))
594 | } else {
595 | Ok(Message::binary(message))
596 | }
597 | }
598 | let frames_stream_write = write
599 | .with(|message| transform(message))
600 | .sink_map_err(|e| ProtocolError::WriteError);
601 |
602 | let (priv_key, pub_key) = generate_keypair();
603 | let master_key: [u8; 32] = [0; 32];
604 | let mut cnx_res = ConnectionRemote::open_broker_connection(
605 | frames_stream_write,
606 | frames_stream_read,
607 | pub_key,
608 | priv_key,
609 | PubKey::Ed25519PubKey([1; 32]),
610 | )
611 | .await;
612 |
613 | match cnx_res {
614 | Ok(mut cnx) => {
615 | if let Err(e) = test(&mut cnx, pub_key, priv_key).await {
616 | debug_println!("error: {:?}", e)
617 | }
618 | else {
619 | cnx.close().await;
620 |
621 | } }
622 | Err(e) => {
623 | debug_println!("cannot connect {:?}", e);
624 | }
625 | }
626 | }
627 | Err(e) => {
628 | debug_println!("Cannot connect: {:?}", e);
629 | }
630 | }
631 | }
632 |
633 | #[xactor::main]
634 | async fn main() -> std::io::Result<()> {
635 | debug_println!("Starting LoFiRe app demo...");
636 |
637 | test_local_connection().await;
638 |
639 | test_remote_connection().await;
640 |
641 | Ok(())
642 | }
643 |
644 | #[cfg(test)]
645 | mod test {
646 |
647 | use assert_cmd::prelude::*;
648 | use futures::task::SpawnExt;
649 | use lofire::store::*;
650 | use lofire::types::*;
651 | use lofire::utils::*;
652 | use std::process::Command;
653 | #[allow(unused_imports)]
654 | use std::time::Duration;
655 | #[allow(unused_imports)]
656 | use std::{fs, thread};
657 | use tempfile::Builder; // Run programs
658 |
659 | use crate::{test_local_connection, test_remote_connection};
660 |
661 | #[async_std::test]
662 | pub async fn test_local_cnx() {
663 | xactor::block_on(test_local_connection());
664 | }
665 |
666 | use async_std::net::{TcpListener, TcpStream};
667 | use async_std::sync::Mutex;
668 | use async_std::task;
669 | use async_tungstenite::accept_async;
670 | use async_tungstenite::tungstenite::protocol::Message;
671 | use debug_print::*;
672 | use futures::{SinkExt, StreamExt};
673 | use lofire_broker::config::ConfigMode;
674 | use lofire_broker::server::*;
675 | use lofire_store_lmdb::brokerstore::LmdbBrokerStore;
676 | use std::sync::Arc;
677 |
678 | // async fn connection_loop(tcp: TcpStream, mut handler: ProtocolHandler) -> std::io::Result<()> {
679 | // let mut ws = accept_async(tcp).await.unwrap();
680 | // let (mut tx, mut rx) = ws.split();
681 |
682 | // let mut tx_mutex = Arc::new(Mutex::new(tx));
683 |
684 | // // setup the async frames task
685 | // let receiver = handler.async_frames_receiver();
686 | // let ws_in_task = Arc::clone(&tx_mutex);
687 | // task::spawn(async move {
688 | // while let Ok(frame) = receiver.recv().await {
689 | // if ws_in_task
690 | // .lock()
691 | // .await
692 | // .send(Message::binary(frame))
693 | // .await
694 | // .is_err()
695 | // {
696 | // //deal with sending errors (close the connection)
697 | // break;
698 | // }
699 | // }
700 | // debug_println!("end of async frames loop");
701 |
702 | // let mut lock = ws_in_task.lock().await;
703 | // let _ = lock.send(Message::Close(None)).await;
704 | // let _ = lock.close();
705 | // });
706 |
707 | // while let Some(msg) = rx.next().await {
708 | // let msg = match msg {
709 | // Err(e) => {
710 | // debug_println!("Error on server stream: {:?}", e);
711 | // // Errors returned directly through the AsyncRead/Write API are fatal, generally an error on the underlying
712 | // // transport.
713 | // // TODO close connection
714 | // break;
715 | // }
716 | // Ok(m) => m,
717 | // };
718 | // //TODO implement PING and CLOSE messages
719 | // if msg.is_close() {
720 | // debug_println!("CLOSE from client");
721 | // break;
722 | // } else if msg.is_binary() {
723 | // //debug_println!("server received binary: {:?}", msg);
724 |
725 | // let replies = handler.handle_incoming(msg.into_data()).await;
726 |
727 | // match replies.0 {
728 | // Err(e) => {
729 | // debug_println!("Protocol Error: {:?}", e);
730 | // // dealing with ProtocolErrors (close the connection)
731 | // break;
732 | // }
733 | // Ok(r) => {
734 | // if tx_mutex
735 | // .lock()
736 | // .await
737 | // .send(Message::binary(r))
738 | // .await
739 | // .is_err()
740 | // {
741 | // //deaingl with sending errors (close the connection)
742 | // break;
743 | // }
744 | // }
745 | // }
746 | // match replies.1.await {
747 | // Some(errcode) => {
748 | // if errcode > 0 {
749 | // debug_println!("Close due to error code : {:?}", errcode);
750 | // //close connection
751 | // break;
752 | // }
753 | // }
754 | // None => {}
755 | // }
756 | // }
757 | // }
758 | // let mut lock = tx_mutex.lock().await;
759 | // let _ = lock.send(Message::Close(None)).await;
760 | // let _ = lock.close();
761 | // debug_println!("end of sync read+write loop");
762 | // Ok(())
763 | // }
764 |
765 | async fn run_server_accept_one() -> std::io::Result<()> {
766 | // let root = tempfile::Builder::new()
767 | // .prefix("node-daemon")
768 | // .tempdir()
769 | // .unwrap();
770 | // let master_key: [u8; 32] = [0; 32];
771 | // std::fs::create_dir_all(root.path()).unwrap();
772 | // println!("{}", root.path().to_str().unwrap());
773 | // let store = LmdbBrokerStore::open(root.path(), master_key);
774 |
775 | // let server: BrokerServer =
776 | // BrokerServer::new(store, ConfigMode::Local).expect("starting broker");
777 |
778 | // let socket = TcpListener::bind("127.0.0.1:3012").await?;
779 | // debug_println!("Listening on 127.0.0.1:3012");
780 | // let mut connections = socket.incoming();
781 | // let server_arc = Arc::new(server);
782 | // let tcp = connections.next().await.unwrap()?;
783 | // let proto_handler = Arc::clone(&server_arc).protocol_handler();
784 | // let _handle = task::spawn(connection_loop(tcp, proto_handler));
785 |
786 | Ok(())
787 | }
788 |
789 | #[async_std::test]
790 | pub async fn test_remote_cnx() -> Result<(), Box> {
791 | //let mut cmd = Command::cargo_bin("lofire-node")?;
792 | //cmd.spawn();
793 |
794 | let thr = task::spawn(run_server_accept_one());
795 |
796 | std::thread::sleep(std::time::Duration::from_secs(2));
797 |
798 | xactor::block_on(test_remote_connection());
799 |
800 | xactor::block_on(thr);
801 |
802 | Ok(())
803 | }
804 | }
805 |
--------------------------------------------------------------------------------
/lofire-net/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lofire-net"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | lofire = { path = "../lofire" }
10 | serde = { version = "1.0", features = ["derive"] }
11 | serde_bare = "0.5.0"
12 | serde_bytes = "0.11.7"
13 | num_enum = "0.5.7"
--------------------------------------------------------------------------------
/lofire-net/src/errors.rs:
--------------------------------------------------------------------------------
1 | use crate::types::BrokerMessage;
2 | use core::fmt;
3 | use lofire::object::ObjectParseError;
4 | use lofire::types::Block;
5 | use lofire::types::ObjectId;
6 | use num_enum::IntoPrimitive;
7 | use num_enum::TryFromPrimitive;
8 | use std::convert::From;
9 | use std::convert::TryFrom;
10 | use std::error::Error;
11 |
12 | #[derive(Debug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive, Clone)]
13 | #[repr(u16)]
14 | pub enum ProtocolError {
15 | WriteError = 1,
16 | ActorError,
17 | InvalidState,
18 | SignatureError,
19 | InvalidSignature,
20 | SerializationError,
21 | PartialContent,
22 | AccessDenied,
23 | OverlayNotJoined,
24 | OverlayNotFound,
25 | BrokerError,
26 | NotFound,
27 | EndOfStream,
28 | StoreError,
29 | MissingBlocks,
30 | ObjectParseError,
31 | InvalidValue,
32 | UserAlreadyExists,
33 | RepoIdRequired,
34 | Closing,
35 | }
36 |
37 | impl ProtocolError {
38 | pub fn is_stream(&self) -> bool {
39 | *self == ProtocolError::PartialContent || *self == ProtocolError::EndOfStream
40 | }
41 | }
42 |
43 | impl Error for ProtocolError {}
44 |
45 | impl fmt::Display for ProtocolError {
46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 | write!(f, "{:?}", self)
48 | }
49 | }
50 |
51 | impl From for ProtocolError {
52 | fn from(e: lofire::errors::LofireError) -> Self {
53 | match e {
54 | lofire::errors::LofireError::InvalidSignature => ProtocolError::InvalidSignature,
55 | lofire::errors::LofireError::SerializationError => ProtocolError::SerializationError,
56 | }
57 | }
58 | }
59 |
60 | impl From for ProtocolError {
61 | fn from(e: ObjectParseError) -> Self {
62 | ProtocolError::ObjectParseError
63 | }
64 | }
65 |
66 | impl From for ProtocolError {
67 | fn from(e: lofire::store::StorageError) -> Self {
68 | match e {
69 | lofire::store::StorageError::NotFound => ProtocolError::NotFound,
70 | lofire::store::StorageError::InvalidValue => ProtocolError::InvalidValue,
71 | _ => ProtocolError::StoreError,
72 | }
73 | }
74 | }
75 |
76 | impl From for ProtocolError {
77 | fn from(e: serde_bare::error::Error) -> Self {
78 | ProtocolError::SerializationError
79 | }
80 | }
81 |
82 | impl From for Result<(), ProtocolError> {
83 | fn from(msg: BrokerMessage) -> Self {
84 | if !msg.is_response() {
85 | panic!("BrokerMessage is not a response");
86 | }
87 | match msg.result() {
88 | 0 => Ok(()),
89 | err => Err(ProtocolError::try_from(err).unwrap()),
90 | }
91 | }
92 | }
93 |
94 | impl From for Result {
95 | fn from(msg: BrokerMessage) -> Self {
96 | if !msg.is_response() {
97 | panic!("BrokerMessage is not a response");
98 | }
99 | match msg.result() {
100 | 0 => Ok(msg.response_object_id()),
101 | err => Err(ProtocolError::try_from(err).unwrap()),
102 | }
103 | }
104 | }
105 |
106 | /// Option represents if a Block is available. cannot be returned here. call BrokerMessage.response_block() to get a reference to it.
107 | impl From for Result