parent
5486f894f4
commit
6087ab253b
@ -0,0 +1,6 @@ |
||||
*~ |
||||
!.github |
||||
\#* |
||||
/target |
||||
/result* |
||||
.DS_Store |
@ -1,10 +1,10 @@ |
||||
[package] |
||||
name = "ng-app-web" |
||||
name = "ng-app-js-sdk" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "Web app client of NextGraph" |
||||
description = "JS app sdk of NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[package.metadata.wasm-pack.profile.release] |
@ -0,0 +1,104 @@ |
||||
# ng-app-js |
||||
|
||||
JS/WASM module of NextGraph (SDK and apps) |
||||
|
||||
## NextGraph |
||||
|
||||
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. |
||||
> |
||||
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with end-to-end encryption, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. |
||||
> |
||||
> More info here [https://nextgraph.org](https://nextgraph.org) |
||||
|
||||
## JS/WASM module |
||||
|
||||
This module is part of the SDK of NextGraph. |
||||
|
||||
It is composed of |
||||
- the npm package `ng-app-js` which is the SDK |
||||
- the plain JS web app `app-web` |
||||
- the React web app `app-react` |
||||
- the node-js app `app-node` |
||||
|
||||
## Support |
||||
|
||||
Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org) |
||||
|
||||
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org) |
||||
|
||||
## For developers |
||||
|
||||
Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/). |
||||
|
||||
``` |
||||
npm i ng-app-js-sdk |
||||
``` |
||||
|
||||
## For contributors |
||||
|
||||
``` |
||||
wasm-pack build --target bundler |
||||
cd pkg |
||||
// if you have access to npm registry and want to publish the package |
||||
// npm publish --access=public |
||||
|
||||
cd .. |
||||
wasm-pack build -t nodejs -d pkg-node |
||||
node prepare-node.js |
||||
cd pkg-node |
||||
// if you have access to npm registry and want to publish the package |
||||
// npm publish --access=public |
||||
``` |
||||
|
||||
### Plain JS web app |
||||
|
||||
``` |
||||
cd ../app-web |
||||
// for local development |
||||
npm install --no-save ../pkg |
||||
// or, for install from npm registry: npm install |
||||
npm start |
||||
``` |
||||
|
||||
Open this URL in browser : [http://localhost:8080](http://localhost:8080) |
||||
|
||||
### React web app |
||||
|
||||
``` |
||||
cd ../app-react |
||||
// for local development |
||||
npm install --no-save ../pkg |
||||
// or, for install from npm registry: npm install |
||||
npm run dev |
||||
``` |
||||
|
||||
Open this URL in browser : [http://localhost:8080](http://localhost:8080) |
||||
|
||||
### NodeJS app |
||||
|
||||
``` |
||||
cd ../app-node |
||||
// for local development |
||||
npm install --no-save ../pkg-node |
||||
// or, for install from npm registry: npm install |
||||
npm run start |
||||
``` |
||||
|
||||
### Contributions license |
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted |
||||
for inclusion in the work by you shall be dual licensed as below, without any |
||||
additional terms or conditions.s |
||||
|
||||
## License |
||||
|
||||
Licensed under either of |
||||
* Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) |
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) |
||||
at your option. |
||||
|
||||
`SPDX-License-Identifier: Apache-2.0 OR MIT` |
||||
|
||||
--- |
||||
|
||||
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073. |
@ -0,0 +1 @@ |
||||
node_modules |
@ -0,0 +1,12 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
const ng = require("ng-app-node-sdk"); |
||||
|
||||
console.log(ng.change("you")); |
@ -0,0 +1,18 @@ |
||||
{ |
||||
"name": "ng-app-node", |
||||
"version": "0.1.0", |
||||
"description": "NodeJS app for NextGraph", |
||||
"main": "index.js", |
||||
"scripts": { |
||||
"start": "node index.js" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://git.nextgraph.org/NextGraph/nextgraph-rs.git" |
||||
}, |
||||
"author": "Niko PLP <niko@nextgraph.org>", |
||||
"license": "(MIT OR Apache-2.0)", |
||||
"dependencies": { |
||||
"ng-app-node-sdk": "^0.1.0" |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"presets": ["@babel/preset-env", "@babel/preset-react"] |
||||
} |
@ -0,0 +1,3 @@ |
||||
node_modules |
||||
dist |
||||
build |
@ -0,0 +1,41 @@ |
||||
# ng-app-react |
||||
|
||||
React app client of NextGraph |
||||
|
||||
## NextGraph |
||||
|
||||
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. |
||||
> |
||||
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with end-to-end encryption, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. |
||||
> |
||||
> More info here [https://nextgraph.org](https://nextgraph.org) |
||||
|
||||
## For contributors |
||||
|
||||
Build the JS SDK |
||||
|
||||
``` |
||||
cd .. |
||||
wasm-pack build --target bundler |
||||
``` |
||||
|
||||
``` |
||||
cd app-react |
||||
npm install --no-save ../pkg |
||||
npm run dev |
||||
``` |
||||
|
||||
Open this URL in browser : [http://localhost:8080](http://localhost:8080) |
||||
|
||||
## License |
||||
|
||||
Licensed under either of |
||||
* Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) |
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) |
||||
at your option. |
||||
|
||||
`SPDX-License-Identifier: Apache-2.0 OR MIT` |
||||
|
||||
--- |
||||
|
||||
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073. |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,31 @@ |
||||
{ |
||||
"name": "ng-app-react", |
||||
"version": "0.1.0", |
||||
"description": "React based web application of NextGraph", |
||||
"main": "src/index.jsx", |
||||
"scripts": { |
||||
"dev": "webpack server" |
||||
}, |
||||
"keywords": [], |
||||
"author": "Niko PLP <niko@nextgraph.org>", |
||||
"license": "MIT/Apache-2.0", |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
}, |
||||
"dependencies": { |
||||
"react": "^18.2.0", |
||||
"react-dom": "^18.2.0", |
||||
"ng-app-js-sdk": "^0.1.0" |
||||
}, |
||||
"devDependencies": { |
||||
"@babel/preset-env": "^7.20.2", |
||||
"@babel/preset-react": "^7.18.6", |
||||
"babel-core": "^6.26.3", |
||||
"babel-loader": "^9.1.2", |
||||
"html-webpack-plugin": "^5.5.0", |
||||
"webpack": "^5.75.0", |
||||
"webpack-cli": "^5.0.1", |
||||
"webpack-dev-server": "^4.11.1" |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
<!-- |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
// All rights reserved. |
||||
// Licensed under the Apache License, Version 2.0 |
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> |
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
||||
// at your option. All files in the project carrying such |
||||
// notice may not be copied, modified, or distributed except |
||||
// according to those terms. |
||||
--> |
||||
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<title>NextGraph</title> |
||||
</head> |
||||
|
||||
<body> |
||||
<div id="root"></div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,38 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
// All rights reserved. |
||||
// Licensed under the Apache License, Version 2.0 |
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> |
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
||||
// at your option. All files in the project carrying such |
||||
// notice may not be copied, modified, or distributed except |
||||
// according to those terms. |
||||
|
||||
import React, { useState } from "react"; |
||||
import ReactDOM from "react-dom"; |
||||
|
||||
const ng_sdk = import("ng-app-js-sdk"); |
||||
|
||||
ng_sdk.then((ng) => { |
||||
const App = () => { |
||||
const [name, setName] = useState(""); |
||||
const handleChange = (e) => { |
||||
setName(ng.change(e.target.value)); |
||||
}; |
||||
const handleClick = () => { |
||||
console.log(name); |
||||
ng.greet(name); |
||||
}; |
||||
|
||||
return ( |
||||
<> |
||||
<div> |
||||
I say: {name}<br/> |
||||
<input type="text" onChange={handleChange} /> |
||||
<button onClick={handleClick}>Say hello!</button> |
||||
</div> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root")); |
||||
}); |
@ -0,0 +1,40 @@ |
||||
const HtmlWebpackPlugin = require("html-webpack-plugin"); |
||||
const path = require("path"); |
||||
|
||||
module.exports = { |
||||
entry: "./src/index.jsx", |
||||
output: { |
||||
path: path.resolve(__dirname, "dist"), |
||||
filename: "bundle.[hash].js", |
||||
}, |
||||
devServer: { |
||||
compress: true, |
||||
port: 8080, |
||||
hot: true, |
||||
static: "./dist", |
||||
historyApiFallback: true, |
||||
open: true, |
||||
}, |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.(js|jsx)$/, |
||||
exclude: /node_modules/, |
||||
use: { |
||||
loader: "babel-loader", |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
plugins: [ |
||||
new HtmlWebpackPlugin({ |
||||
template: __dirname + "/public/index.html", |
||||
filename: "index.html", |
||||
}), |
||||
], |
||||
experiments: { |
||||
asyncWebAssembly: true |
||||
}, |
||||
mode: "development", |
||||
devtool: "inline-source-map", |
||||
}; |
@ -0,0 +1,2 @@ |
||||
node_modules |
||||
dist |
@ -0,0 +1,201 @@ |
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
@ -0,0 +1,25 @@ |
||||
Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
|
||||
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. |
@ -0,0 +1,15 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
|
||||
// A dependency graph that contains any wasm must all be imported
|
||||
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||
// that no one else needs to worry about it again.
|
||||
import("./index.js") |
||||
.catch(e => console.error("Error importing `index.js`:", e)); |
@ -0,0 +1,22 @@ |
||||
|
||||
<!-- |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
// All rights reserved. |
||||
// Licensed under the Apache License, Version 2.0 |
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> |
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
||||
// at your option. All files in the project carrying such |
||||
// notice may not be copied, modified, or distributed except |
||||
// according to those terms. |
||||
--> |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<title>NextGraph</title> |
||||
</head> |
||||
<body> |
||||
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript> |
||||
<script src="./bootstrap.js"></script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,12 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
import * as ng from "ng-app-js-sdk"; |
||||
|
||||
console.log(ng.change("you")); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@ |
||||
{ |
||||
"name": "ng-app-web", |
||||
"version": "0.1.0", |
||||
"description": "Web app client of NextGraph", |
||||
"main": "index.js", |
||||
"scripts": { |
||||
"build": "webpack --config webpack.config.js", |
||||
"start": "webpack-dev-server" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://git.nextgraph.org/NextGraph/nextgraph-rs.git" |
||||
}, |
||||
"keywords": [ |
||||
"webassembly", |
||||
"wasm", |
||||
"rust", |
||||
"webpack" |
||||
], |
||||
"author": "Niko PLP <niko@nextgraph.org>", |
||||
"license": "(MIT OR Apache-2.0)", |
||||
"bugs": { |
||||
"url": "https://git.nextgraph.org/NextGraph/nextgraph-rs/issues" |
||||
}, |
||||
"homepage": "https://docs.nextgraph.org", |
||||
"dependencies": { |
||||
"ng-app-js-sdk": "^0.1.0" |
||||
}, |
||||
"devDependencies": { |
||||
"webpack": "^4.29.3", |
||||
"webpack-cli": "^3.1.0", |
||||
"webpack-dev-server": "^3.1.5", |
||||
"copy-webpack-plugin": "^5.0.0" |
||||
} |
||||
} |
@ -0,0 +1,14 @@ |
||||
const CopyWebpackPlugin = require("copy-webpack-plugin"); |
||||
const path = require('path'); |
||||
|
||||
module.exports = { |
||||
entry: "./bootstrap.js", |
||||
output: { |
||||
path: path.resolve(__dirname, "dist"), |
||||
filename: "bootstrap.js", |
||||
}, |
||||
mode: "development", |
||||
plugins: [ |
||||
new CopyWebpackPlugin(['index.html']) |
||||
], |
||||
}; |
@ -0,0 +1,27 @@ |
||||
<!-- |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
// All rights reserved. |
||||
// Licensed under the Apache License, Version 2.0 |
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> |
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
||||
// at your option. All files in the project carrying such |
||||
// notice may not be copied, modified, or distributed except |
||||
// according to those terms. |
||||
--> |
||||
|
||||
<!DOCTYPE html> |
||||
<html lang="en-US"> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<title>NextGraph web sdk test</title> |
||||
</head> |
||||
<body> |
||||
<p>run <code>python3 -m http.server</code> to use it</p> |
||||
<script type="module"> |
||||
import init, { greet } from "./pkg/ng_app_js_sdk.js"; |
||||
init().then(() => { |
||||
greet("WebAssembly"); |
||||
}); |
||||
</script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,9 @@ |
||||
const fs = require('fs'); |
||||
|
||||
const PATH = './pkg-node/package.json'; |
||||
|
||||
const pkg_json = fs.readFileSync(PATH); |
||||
let pkg = JSON.parse(pkg_json) |
||||
pkg.name = "ng-app-node-sdk"; |
||||
pkg.description = "nodejs app sdk of NextGraph"; |
||||
fs.writeFileSync(PATH, JSON.stringify(pkg, null, 2), 'utf8'); |
@ -0,0 +1,16 @@ |
||||
use wasm_bindgen::prelude::*; |
||||
|
||||
#[wasm_bindgen] |
||||
extern { |
||||
pub fn alert(s: &str); |
||||
} |
||||
|
||||
#[wasm_bindgen] |
||||
pub fn greet(name: &str) { |
||||
alert(&format!("I say: {}", name)); |
||||
} |
||||
|
||||
#[wasm_bindgen] |
||||
pub fn change(name: &str) -> JsValue { |
||||
JsValue::from_str(&format!("Hellooo, {}!", name)) |
||||
} |
@ -1,15 +0,0 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en-US"> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<title>hello-wasm example</title> |
||||
</head> |
||||
<body> |
||||
<script type="module"> |
||||
import init, { greet } from "./pkg/ng_app_web.js"; |
||||
init().then(() => { |
||||
greet("WebAssembly"); |
||||
}); |
||||
</script> |
||||
</body> |
||||
</html> |
@ -1,11 +0,0 @@ |
||||
use wasm_bindgen::prelude::*; |
||||
|
||||
#[wasm_bindgen] |
||||
extern { |
||||
pub fn alert(s: &str); |
||||
} |
||||
|
||||
#[wasm_bindgen] |
||||
pub fn greet(name: &str) { |
||||
alert(&format!("Hello, {}!", name)); |
||||
} |
@ -0,0 +1,24 @@ |
||||
[package] |
||||
name = "ngcli" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "CLI command-line interpreter of NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[dependencies] |
||||
debug_print = "1.0.0" |
||||
p2p-repo = { path = "../p2p-repo" } |
||||
p2p-net = { path = "../p2p-net" } |
||||
p2p-client = { path = "../p2p-client" } |
||||
p2p-broker = { path = "../p2p-broker" } |
||||
p2p-stores-lmdb = { path = "../p2p-stores-lmdb" } |
||||
async-std = { version = "1.7.0", features = ["attributes"] } |
||||
futures = "0.3.24" |
||||
xactor = "0.7.11" |
||||
tempfile = "3" |
||||
fastbloom-rs = "0.3.1" |
||||
rand = "0.7" |
||||
ed25519-dalek = "1.0.1" |
||||
assert_cmd = "2.0.5" |
@ -0,0 +1,636 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use debug_print::*; |
||||
use ed25519_dalek::*; |
||||
use fastbloom_rs::{BloomFilter as Filter, FilterBuilder, Membership}; |
||||
use futures::{future, pin_mut, stream, SinkExt, StreamExt}; |
||||
use p2p_repo::object::Object; |
||||
use p2p_repo::store::{store_max_value_size, store_valid_value_size, HashMapRepoStore, RepoStore}; |
||||
use p2p_broker::broker_store_config::ConfigMode; |
||||
use p2p_stores_lmdb::broker_store::LmdbBrokerStore; |
||||
use p2p_stores_lmdb::repo_store::LmdbRepoStore; |
||||
use rand::rngs::OsRng; |
||||
use std::collections::HashMap; |
||||
|
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::{generate_keypair, now_timestamp}; |
||||
use p2p_broker::server_ws::*; |
||||
use p2p_broker::server::*; |
||||
use p2p_net::errors::*; |
||||
use p2p_net::types::*; |
||||
use p2p_net::broker_connection::*; |
||||
use p2p_client::connection_remote::*; |
||||
use p2p_client::connection_ws::*; |
||||
use p2p_broker::connection_local::*; |
||||
|
||||
fn block_size() -> usize { |
||||
store_max_value_size() |
||||
//store_valid_value_size(0)
|
||||
} |
||||
|
||||
async fn test_sync(cnx: &mut impl BrokerConnection, user_pub_key: PubKey, userpriv_key: PrivKey) { |
||||
fn add_obj( |
||||
content: ObjectContent, |
||||
deps: Vec<ObjectId>, |
||||
expiry: Option<Timestamp>, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let max_object_size = 4000; |
||||
let obj = Object::new( |
||||
content, |
||||
deps, |
||||
expiry, |
||||
max_object_size, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
); |
||||
//println!(">>> add_obj");
|
||||
println!(" id: {}", obj.id()); |
||||
//println!(" deps: {:?}", obj.deps());
|
||||
obj.save(store).unwrap(); |
||||
obj.reference().unwrap() |
||||
} |
||||
|
||||
fn add_commit( |
||||
branch: ObjectRef, |
||||
author_privkey: PrivKey, |
||||
author_pubkey: PubKey, |
||||
seq: u32, |
||||
deps: Vec<ObjectRef>, |
||||
acks: Vec<ObjectRef>, |
||||
body_ref: ObjectRef, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let mut obj_deps: Vec<ObjectId> = vec![]; |
||||
obj_deps.extend(deps.iter().map(|r| r.id)); |
||||
obj_deps.extend(acks.iter().map(|r| r.id)); |
||||
|
||||
let obj_ref = ObjectRef { |
||||
id: ObjectId::Blake3Digest32([1; 32]), |
||||
key: SymKey::ChaCha20Key([2; 32]), |
||||
}; |
||||
let refs = vec![obj_ref]; |
||||
let metadata = vec![5u8; 55]; |
||||
let expiry = None; |
||||
|
||||
let commit = Commit::new( |
||||
author_privkey, |
||||
author_pubkey, |
||||
seq, |
||||
branch, |
||||
deps, |
||||
acks, |
||||
refs, |
||||
metadata, |
||||
body_ref, |
||||
expiry, |
||||
) |
||||
.unwrap(); |
||||
//println!("commit: {}", commit.id().unwrap());
|
||||
add_obj( |
||||
ObjectContent::Commit(commit), |
||||
obj_deps, |
||||
expiry, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
store, |
||||
) |
||||
} |
||||
|
||||
fn add_body_branch( |
||||
branch: Branch, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let deps = vec![]; |
||||
let expiry = None; |
||||
let body = CommitBody::Branch(branch); |
||||
//println!("body: {:?}", body);
|
||||
add_obj( |
||||
ObjectContent::CommitBody(body), |
||||
deps, |
||||
expiry, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
store, |
||||
) |
||||
} |
||||
|
||||
fn add_body_trans( |
||||
deps: Vec<ObjectId>, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let expiry = None; |
||||
let content = [7u8; 777].to_vec(); |
||||
let body = CommitBody::Transaction(Transaction::V0(content)); |
||||
//println!("body: {:?}", body);
|
||||
add_obj( |
||||
ObjectContent::CommitBody(body), |
||||
deps, |
||||
expiry, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
store, |
||||
) |
||||
} |
||||
|
||||
fn add_body_ack( |
||||
deps: Vec<ObjectId>, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let expiry = None; |
||||
let body = CommitBody::Ack(Ack::V0()); |
||||
//println!("body: {:?}", body);
|
||||
add_obj( |
||||
ObjectContent::CommitBody(body), |
||||
deps, |
||||
expiry, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
store, |
||||
) |
||||
} |
||||
|
||||
let mut store = HashMapRepoStore::new(); |
||||
let mut rng = OsRng {}; |
||||
|
||||
// repo
|
||||
|
||||
let repo_keypair: Keypair = Keypair::generate(&mut rng); |
||||
// println!(
|
||||
// "repo private key: ({}) {:?}",
|
||||
// repo_keypair.secret.as_bytes().len(),
|
||||
// repo_keypair.secret.as_bytes()
|
||||
// );
|
||||
// println!(
|
||||
// "repo public key: ({}) {:?}",
|
||||
// repo_keypair.public.as_bytes().len(),
|
||||
// repo_keypair.public.as_bytes()
|
||||
// );
|
||||
let _repo_privkey = PrivKey::Ed25519PrivKey(repo_keypair.secret.to_bytes()); |
||||
let repo_pubkey = PubKey::Ed25519PubKey(repo_keypair.public.to_bytes()); |
||||
let repo_secret = SymKey::ChaCha20Key([9; 32]); |
||||
|
||||
let repolink = RepoLink::V0(RepoLinkV0 { |
||||
id: repo_pubkey, |
||||
secret: repo_secret, |
||||
peers: vec![], |
||||
}); |
||||
|
||||
// branch
|
||||
|
||||
let branch_keypair: Keypair = Keypair::generate(&mut rng); |
||||
//println!("branch public key: {:?}", branch_keypair.public.as_bytes());
|
||||
let branch_pubkey = PubKey::Ed25519PubKey(branch_keypair.public.to_bytes()); |
||||
|
||||
let member_keypair: Keypair = Keypair::generate(&mut rng); |
||||
//println!("member public key: {:?}", member_keypair.public.as_bytes());
|
||||
let member_privkey = PrivKey::Ed25519PrivKey(member_keypair.secret.to_bytes()); |
||||
let member_pubkey = PubKey::Ed25519PubKey(member_keypair.public.to_bytes()); |
||||
|
||||
let metadata = [66u8; 64].to_vec(); |
||||
let commit_types = vec![CommitType::Ack, CommitType::Transaction]; |
||||
let secret = SymKey::ChaCha20Key([0; 32]); |
||||
|
||||
let member = MemberV0::new(member_pubkey, commit_types, metadata.clone()); |
||||
let members = vec![member]; |
||||
let mut quorum = HashMap::new(); |
||||
quorum.insert(CommitType::Transaction, 3); |
||||
let ack_delay = RelTime::Minutes(3); |
||||
let tags = [99u8; 32].to_vec(); |
||||
let branch = Branch::new( |
||||
branch_pubkey, |
||||
branch_pubkey, |
||||
secret, |
||||
members, |
||||
quorum, |
||||
ack_delay, |
||||
tags, |
||||
metadata, |
||||
); |
||||
//println!("branch: {:?}", branch);
|
||||
|
||||
println!("branch deps/acks:"); |
||||
println!(""); |
||||
println!(" br"); |
||||
println!(" / \\"); |
||||
println!(" t1 t2"); |
||||
println!(" / \\ / \\"); |
||||
println!(" a3 t4<--t5-->(t1)"); |
||||
println!(" / \\"); |
||||
println!(" a6 a7"); |
||||
println!(""); |
||||
|
||||
// commit bodies
|
||||
|
||||
let branch_body = add_body_branch( |
||||
branch.clone(), |
||||
repo_pubkey.clone(), |
||||
repo_secret.clone(), |
||||
&mut store, |
||||
); |
||||
let ack_body = add_body_ack(vec![], repo_pubkey, repo_secret, &mut store); |
||||
let trans_body = add_body_trans(vec![], repo_pubkey, repo_secret, &mut store); |
||||
|
||||
// create & add commits to store
|
||||
|
||||
println!(">> br"); |
||||
let br = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
0, |
||||
vec![], |
||||
vec![], |
||||
branch_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> t1"); |
||||
let t1 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
1, |
||||
vec![br], |
||||
vec![], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> t2"); |
||||
let t2 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
2, |
||||
vec![br], |
||||
vec![], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> a3"); |
||||
let a3 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
3, |
||||
vec![t1], |
||||
vec![], |
||||
ack_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> t4"); |
||||
let t4 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
4, |
||||
vec![t2], |
||||
vec![t1], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> t5"); |
||||
let t5 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
5, |
||||
vec![t1, t2], |
||||
vec![t4], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> a6"); |
||||
let a6 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
6, |
||||
vec![t4], |
||||
vec![], |
||||
ack_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> a7"); |
||||
let a7 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
7, |
||||
vec![t4], |
||||
vec![], |
||||
ack_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
let mut public_overlay_cnx = cnx |
||||
.overlay_connect(&repolink, true) |
||||
.await |
||||
.expect("overlay_connect failed"); |
||||
|
||||
// Sending everything to the broker
|
||||
for (v) in store.get_all() { |
||||
//debug_println!("SENDING {}", k);
|
||||
let _ = public_overlay_cnx |
||||
.put_block(&v) |
||||
.await |
||||
.expect("put_block failed"); |
||||
} |
||||
|
||||
// Now emptying the local store of the client, and adding only 1 commit into it (br)
|
||||
// we also have received an commit (t5) but we don't know what to do with it...
|
||||
let mut store = HashMapRepoStore::new(); |
||||
|
||||
let br = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
0, |
||||
vec![], |
||||
vec![], |
||||
branch_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
let t5 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
5, |
||||
vec![t1, t2], |
||||
vec![t4], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
debug_println!("LOCAL STORE HAS {} BLOCKS", store.get_len()); |
||||
|
||||
// Let's pretend that we know that the head of the branch in the broker is at commits a6 and a7.
|
||||
// normally it would be the pub/sub that notifies us of those heads.
|
||||
// now we want to synchronize with the broker.
|
||||
|
||||
let mut filter = Filter::new(FilterBuilder::new(10, 0.01)); |
||||
for commit_ref in [br, t5] { |
||||
match commit_ref.id { |
||||
ObjectId::Blake3Digest32(d) => filter.add(&d), |
||||
} |
||||
} |
||||
let cfg = filter.config(); |
||||
|
||||
let known_commits = BloomFilter { |
||||
k: cfg.hashes, |
||||
f: filter.get_u8_array().to_vec(), |
||||
}; |
||||
|
||||
let known_heads = [br.id]; |
||||
|
||||
let remote_heads = [a6.id, a7.id]; |
||||
|
||||
let mut synced_blocks_stream = public_overlay_cnx |
||||
.sync_branch(remote_heads.to_vec(), known_heads.to_vec(), known_commits) |
||||
.await |
||||
.expect("sync_branch failed"); |
||||
|
||||
let mut i = 0; |
||||
while let Some(b) = synced_blocks_stream.next().await { |
||||
debug_println!("GOT BLOCK {}", b.id()); |
||||
store.put(&b); |
||||
i += 1; |
||||
} |
||||
|
||||
debug_println!("SYNCED {} BLOCKS", i); |
||||
|
||||
debug_println!("LOCAL STORE HAS {} BLOCKS", store.get_len()); |
||||
|
||||
// now the client can verify the DAG and each commit. Then update its list of heads.
|
||||
} |
||||
|
||||
async fn test(cnx: &mut impl BrokerConnection, pub_key: PubKey, priv_key: PrivKey) -> Result<(), ProtocolError>{ |
||||
|
||||
cnx.add_user(PubKey::Ed25519PubKey([1; 32]), priv_key).await?; |
||||
|
||||
cnx.add_user(pub_key, priv_key).await?; |
||||
//.expect("add_user 2 (myself) failed");
|
||||
|
||||
assert_eq!( |
||||
cnx.add_user(PubKey::Ed25519PubKey([1; 32]), priv_key).await.err().unwrap(), |
||||
ProtocolError::UserAlreadyExists |
||||
); |
||||
|
||||
let repo = RepoLink::V0(RepoLinkV0 { |
||||
id: PubKey::Ed25519PubKey([1; 32]), |
||||
secret: SymKey::ChaCha20Key([0; 32]), |
||||
peers: vec![], |
||||
}); |
||||
let mut public_overlay_cnx = cnx |
||||
.overlay_connect(&repo, true) |
||||
.await?; |
||||
|
||||
let my_block_id = public_overlay_cnx |
||||
.put_block(&Block::new( |
||||
vec![], |
||||
ObjectDeps::ObjectIdList(vec![]), |
||||
None, |
||||
vec![27; 150], |
||||
None, |
||||
)) |
||||
.await?; |
||||
|
||||
debug_println!("added block_id to store {}", my_block_id); |
||||
|
||||
let object_id = public_overlay_cnx |
||||
.put_object( |
||||
ObjectContent::File(File::V0(FileV0 { |
||||
content_type: vec![], |
||||
metadata: vec![], |
||||
content: vec![48; 69000], |
||||
})), |
||||
vec![], |
||||
None, |
||||
block_size(), |
||||
repo.id(), |
||||
repo.secret(), |
||||
) |
||||
.await?; |
||||
|
||||
debug_println!("added object_id to store {}", object_id); |
||||
|
||||
let mut my_block_stream = public_overlay_cnx |
||||
.get_block(my_block_id, true, None) |
||||
.await?; |
||||
//.expect("get_block failed");
|
||||
|
||||
while let Some(b) = my_block_stream.next().await { |
||||
debug_println!("GOT BLOCK {}", b.id()); |
||||
} |
||||
|
||||
let mut my_object_stream = public_overlay_cnx |
||||
.get_block(object_id, true, None) |
||||
.await?; |
||||
//.expect("get_block for object failed");
|
||||
|
||||
while let Some(b) = my_object_stream.next().await { |
||||
debug_println!("GOT BLOCK {}", b.id()); |
||||
} |
||||
|
||||
let object = public_overlay_cnx |
||||
.get_object(object_id, None) |
||||
.await?; |
||||
//.expect("get_object failed");
|
||||
|
||||
debug_println!("GOT OBJECT with ID {}", object.id()); |
||||
|
||||
// let object_id = public_overlay_cnx
|
||||
// .copy_object(object_id, Some(now_timestamp() + 60))
|
||||
// .await
|
||||
// .expect("copy_object failed");
|
||||
|
||||
// debug_println!("COPIED OBJECT to OBJECT ID {}", object_id);
|
||||
|
||||
public_overlay_cnx |
||||
.delete_object(object_id) |
||||
.await?; |
||||
//.expect("delete_object failed");
|
||||
|
||||
let res = public_overlay_cnx |
||||
.get_object(object_id, None) |
||||
.await |
||||
.unwrap_err(); |
||||
|
||||
debug_println!("result from get object after delete: {}", res); |
||||
assert_eq!(res, ProtocolError::NotFound); |
||||
|
||||
//TODO test pin/unpin
|
||||
|
||||
// TEST BRANCH SYNC
|
||||
|
||||
test_sync(cnx, pub_key, priv_key).await; |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
async fn test_local_connection() { |
||||
debug_println!("===== TESTING LOCAL API ====="); |
||||
|
||||
let root = tempfile::Builder::new() |
||||
.prefix("node-daemon") |
||||
.tempdir() |
||||
.unwrap(); |
||||
let master_key: [u8; 32] = [0; 32]; |
||||
std::fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let store = LmdbBrokerStore::open(root.path(), master_key); |
||||
|
||||
let mut server = BrokerServer::new(store, ConfigMode::Local).expect("starting broker"); |
||||
|
||||
let (priv_key, pub_key) = generate_keypair(); |
||||
|
||||
let mut cnx = server.local_connection(pub_key); |
||||
|
||||
test(&mut cnx, pub_key, priv_key).await; |
||||
} |
||||
|
||||
async fn test_remote_connection(url: &str) { |
||||
debug_println!("===== TESTING REMOTE API ====="); |
||||
|
||||
let (priv_key, pub_key) = generate_keypair(); |
||||
let cnx_res = BrokerConnectionWebSocket::open(url, priv_key, pub_key).await; |
||||
match cnx_res { |
||||
Ok(mut cnx) => { |
||||
if let Err(e) = test(&mut cnx, pub_key, priv_key).await { |
||||
debug_println!("error: {:?}", e) |
||||
} |
||||
else { |
||||
cnx.close().await; |
||||
|
||||
} } |
||||
Err(e) => { |
||||
|
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
#[xactor::main] |
||||
async fn main() -> std::io::Result<()> { |
||||
debug_println!("Starting nextgraph CLI..."); |
||||
|
||||
test_local_connection().await; |
||||
|
||||
test_remote_connection("ws://127.0.0.1:3012").await; |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod test { |
||||
|
||||
use crate::{test_local_connection, test_remote_connection}; |
||||
|
||||
#[async_std::test] |
||||
pub async fn test_local_cnx() { |
||||
xactor::block_on(test_local_connection()); |
||||
} |
||||
|
||||
use async_std::task; |
||||
use p2p_broker::server_ws::*; |
||||
|
||||
#[async_std::test] |
||||
pub async fn test_remote_cnx() -> Result<(), Box<dyn std::error::Error>> { |
||||
|
||||
let thr = task::spawn(run_server_accept_one("127.0.0.1:3012")); |
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(2)); |
||||
|
||||
xactor::block_on(test_remote_connection("ws://127.0.0.1:3012")); |
||||
|
||||
xactor::block_on(thr); |
||||
|
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
[package] |
||||
name = "ngd" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "Daemon of NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[dependencies] |
||||
p2p-broker = { path = "../p2p-broker" } |
||||
async-std = { version = "1.7.0", features = ["attributes"] } |
@ -0,0 +1,18 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use p2p_broker::server_ws::run_server; |
||||
|
||||
|
||||
#[async_std::main] |
||||
async fn main() -> std::io::Result<()> { |
||||
println!("Starting NextGraph daemon..."); |
||||
|
||||
run_server("127.0.0.1:3012").await |
||||
} |
@ -0,0 +1,27 @@ |
||||
[package] |
||||
name = "p2p-broker" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "P2P Broker module of NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[dependencies] |
||||
debug_print = "1.0.0" |
||||
p2p-repo = { path = "../p2p-repo" } |
||||
p2p-net = { path = "../p2p-net" } |
||||
p2p-stores-lmdb = { path = "../p2p-stores-lmdb" } |
||||
chacha20 = "0.9.0" |
||||
serde = { version = "1.0", features = ["derive"] } |
||||
serde_bare = "0.5.0" |
||||
serde_bytes = "0.11.7" |
||||
async-std = { version = "1.7.0", features = ["attributes"] } |
||||
futures = "0.3.24" |
||||
rust-fsm = "0.6.0" |
||||
getrandom = "0.2.7" |
||||
async-channel = "1.7.1" |
||||
tempfile = "3" |
||||
hex = "0.4.3" |
||||
async-trait = "0.1.57" |
||||
async-tungstenite = { version = "0.17.2", features = ["async-std-runtime","async-native-tls"] } |
@ -0,0 +1,178 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::pin::Pin; |
||||
|
||||
use debug_print::*; |
||||
use futures::future::BoxFuture; |
||||
use futures::future::OptionFuture; |
||||
use futures::FutureExt; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
use p2p_net::errors::*; |
||||
use p2p_net::types::*; |
||||
use rust_fsm::*; |
||||
|
||||
state_machine! { |
||||
derive(Debug) |
||||
AuthProtocolClient(Ready) |
||||
|
||||
Ready(ClientHelloSent) => ClientHelloSent, |
||||
ClientHelloSent(ServerHelloReceived) => ServerHelloReceived, |
||||
ServerHelloReceived(ClientAuthSent) => ClientAuthSent, |
||||
ClientAuthSent(AuthResultReceived) => AuthResult, |
||||
AuthResult => { |
||||
Ok => BrokerProtocol, |
||||
Error => Closed, |
||||
}, |
||||
} |
||||
|
||||
state_machine! { |
||||
derive(Debug) |
||||
AuthProtocolServer(Ready) |
||||
|
||||
Ready(ClientHelloReceived) => ClientHelloReceived, |
||||
ClientHelloReceived(ServerHelloSent) => ServerHelloSent, |
||||
ServerHelloSent(ClientAuthReceived) => ClientAuthReceived, |
||||
ClientAuthReceived => { |
||||
Ok => AuthResultOk, |
||||
Error => AuthResultError, |
||||
}, |
||||
AuthResultOk(AuthResultSent) => BrokerProtocol, |
||||
AuthResultError(AuthResultSent) => Closed, |
||||
} |
||||
|
||||
pub struct AuthProtocolHandler { |
||||
machine: StateMachine<AuthProtocolServer>, |
||||
nonce: Option<Vec<u8>>, |
||||
user: Option<PubKey>, |
||||
} |
||||
|
||||
impl AuthProtocolHandler { |
||||
pub fn new() -> AuthProtocolHandler { |
||||
AuthProtocolHandler { |
||||
machine: StateMachine::new(), |
||||
nonce: None, |
||||
user: None, |
||||
} |
||||
} |
||||
|
||||
pub fn get_user(&self) -> Option<PubKey> { |
||||
self.user |
||||
} |
||||
|
||||
pub fn handle_init(&mut self, client_hello: ClientHello) -> Result<Vec<u8>, ProtocolError> { |
||||
let _ = self |
||||
.machine |
||||
.consume(&AuthProtocolServerInput::ClientHelloReceived) |
||||
.map_err(|_e| ProtocolError::InvalidState)?; |
||||
|
||||
let mut random_buf = [0u8; 32]; |
||||
getrandom::getrandom(&mut random_buf).unwrap(); |
||||
let nonce = random_buf.to_vec(); |
||||
let reply = ServerHello::V0(ServerHelloV0 { |
||||
nonce: nonce.clone(), |
||||
}); |
||||
self.nonce = Some(nonce); |
||||
|
||||
let _ = self |
||||
.machine |
||||
.consume(&AuthProtocolServerInput::ServerHelloSent) |
||||
.map_err(|_e| ProtocolError::InvalidState)?; |
||||
|
||||
//debug_println!("sending nonce to client: {:?}", self.nonce);
|
||||
|
||||
Ok(serde_bare::to_vec(&reply).unwrap()) |
||||
} |
||||
|
||||
pub fn handle_incoming( |
||||
&mut self, |
||||
frame: Vec<u8>, |
||||
) -> ( |
||||
Result<Vec<u8>, ProtocolError>, |
||||
Pin<Box<OptionFuture<BoxFuture<'static, u16>>>>, |
||||
) { |
||||
fn prepare_reply(res: Result<Vec<u8>, ProtocolError>) -> AuthResult { |
||||
let (result, metadata) = match res { |
||||
Ok(m) => (0, m), |
||||
Err(e) => (e.into(), vec![]), |
||||
}; |
||||
AuthResult::V0(AuthResultV0 { result, metadata }) |
||||
} |
||||
|
||||
fn process_state( |
||||
handler: &mut AuthProtocolHandler, |
||||
frame: Vec<u8>, |
||||
) -> Result<Vec<u8>, ProtocolError> { |
||||
match handler.machine.state() { |
||||
&AuthProtocolServerState::ServerHelloSent => { |
||||
let message = serde_bare::from_slice::<ClientAuth>(&frame)?; |
||||
let _ = handler |
||||
.machine |
||||
.consume(&AuthProtocolServerInput::ClientAuthReceived) |
||||
.map_err(|_e| ProtocolError::InvalidState)?; |
||||
|
||||
// verifying client auth
|
||||
|
||||
debug_println!("verifying client auth"); |
||||
|
||||
let _ = verify( |
||||
&serde_bare::to_vec(&message.content_v0()).unwrap(), |
||||
message.sig(), |
||||
message.user(), |
||||
) |
||||
.map_err(|_e| ProtocolError::AccessDenied)?; |
||||
|
||||
// debug_println!(
|
||||
// "matching nonce : {:?} {:?}",
|
||||
// message.nonce(),
|
||||
// handler.nonce.as_ref().unwrap()
|
||||
// );
|
||||
|
||||
if message.nonce() != handler.nonce.as_ref().unwrap() { |
||||
let _ = handler |
||||
.machine |
||||
.consume(&AuthProtocolServerInput::Error) |
||||
.map_err(|_e| ProtocolError::InvalidState); |
||||
|
||||
return Err(ProtocolError::AccessDenied); |
||||
} |
||||
|
||||
// TODO check that the device has been registered for this user. if not, return AccessDenied
|
||||
|
||||
// all is good, we advance the FSM and send back response
|
||||
let _ = handler |
||||
.machine |
||||
.consume(&AuthProtocolServerInput::Ok) |
||||
.map_err(|_e| ProtocolError::InvalidState)?; |
||||
|
||||
handler.user = Some(message.user()); |
||||
|
||||
Ok(vec![]) // without any metadata
|
||||
} |
||||
_ => Err(ProtocolError::InvalidState), |
||||
} |
||||
} |
||||
|
||||
let res = process_state(self, frame); |
||||
let is_err = res.as_ref().err().cloned(); |
||||
let reply = prepare_reply(res); |
||||
let reply_ser: Result<Vec<u8>, ProtocolError> = Ok(serde_bare::to_vec(&reply).unwrap()); |
||||
if is_err.is_some() { |
||||
( |
||||
reply_ser, |
||||
Box::pin(OptionFuture::from(Some( |
||||
async move { reply.result() }.boxed(), |
||||
))), |
||||
) |
||||
} else { |
||||
(reply_ser, Box::pin(OptionFuture::from(None))) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,197 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! User account
|
||||
|
||||
use p2p_repo::broker_store::BrokerStore; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_net::types::*; |
||||
use serde_bare::to_vec; |
||||
|
||||
pub struct Account<'a> { |
||||
/// User ID
|
||||
id: UserId, |
||||
store: &'a dyn BrokerStore, |
||||
} |
||||
|
||||
impl<'a> Account<'a> { |
||||
const PREFIX: u8 = b"u"[0]; |
||||
|
||||
// propertie's suffixes
|
||||
const CLIENT: u8 = b"c"[0]; |
||||
const ADMIN: u8 = b"a"[0]; |
||||
const OVERLAY: u8 = b"o"[0]; |
||||
|
||||
const ALL_PROPERTIES: [u8; 3] = [Self::CLIENT, Self::ADMIN, Self::OVERLAY]; |
||||
|
||||
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::ADMIN; |
||||
|
||||
pub fn open(id: &UserId, store: &'a dyn BrokerStore) -> Result<Account<'a>, StorageError> { |
||||
let opening = Account { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if !opening.exists() { |
||||
return Err(StorageError::NotFound); |
||||
} |
||||
Ok(opening) |
||||
} |
||||
pub fn create( |
||||
id: &UserId, |
||||
admin: bool, |
||||
store: &'a dyn BrokerStore, |
||||
) -> Result<Account<'a>, StorageError> { |
||||
let acc = Account { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if acc.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&id)?, |
||||
Some(Self::ADMIN), |
||||
to_vec(&admin)?, |
||||
)?; |
||||
Ok(acc) |
||||
} |
||||
pub fn exists(&self) -> bool { |
||||
self.store |
||||
.get( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id).unwrap(), |
||||
Some(Self::SUFFIX_FOR_EXIST_CHECK), |
||||
) |
||||
.is_ok() |
||||
} |
||||
pub fn id(&self) -> UserId { |
||||
self.id |
||||
} |
||||
pub fn add_client(&self, client: &ClientId) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::CLIENT), |
||||
to_vec(client)?, |
||||
) |
||||
} |
||||
pub fn remove_client(&self, client: &ClientId) -> Result<(), StorageError> { |
||||
self.store.del_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::CLIENT), |
||||
to_vec(client)?, |
||||
) |
||||
} |
||||
|
||||
pub fn has_client(&self, client: &ClientId) -> Result<(), StorageError> { |
||||
self.store.has_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::CLIENT), |
||||
to_vec(client)?, |
||||
) |
||||
} |
||||
|
||||
pub fn add_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::OVERLAY), |
||||
to_vec(overlay)?, |
||||
) |
||||
} |
||||
pub fn remove_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> { |
||||
self.store.del_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::OVERLAY), |
||||
to_vec(overlay)?, |
||||
) |
||||
} |
||||
|
||||
pub fn has_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> { |
||||
self.store.has_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::OVERLAY), |
||||
to_vec(overlay)?, |
||||
) |
||||
} |
||||
|
||||
pub fn is_admin(&self) -> Result<bool, StorageError> { |
||||
if self |
||||
.store |
||||
.has_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::ADMIN), |
||||
to_vec(&true)?, |
||||
) |
||||
.is_ok() |
||||
{ |
||||
return Ok(true); |
||||
} |
||||
Ok(false) |
||||
} |
||||
|
||||
pub fn del(&self) -> Result<(), StorageError> { |
||||
self.store |
||||
.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod test { |
||||
|
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
use p2p_stores_lmdb::broker_store::LmdbBrokerStore; |
||||
use std::fs; |
||||
use tempfile::Builder; |
||||
|
||||
use crate::broker_store_account::Account; |
||||
|
||||
#[test] |
||||
pub fn test_account() { |
||||
let path_str = "test-env"; |
||||
let root = Builder::new().prefix(path_str).tempdir().unwrap(); |
||||
let key: [u8; 32] = [0; 32]; |
||||
fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let mut store = LmdbBrokerStore::open(root.path(), key); |
||||
|
||||
let user_id = PubKey::Ed25519PubKey([1; 32]); |
||||
|
||||
let account = Account::create(&user_id, true, &store).unwrap(); |
||||
println!("account created {}", account.id()); |
||||
|
||||
let account2 = Account::open(&user_id, &store).unwrap(); |
||||
println!("account opened {}", account2.id()); |
||||
|
||||
let client_id = PubKey::Ed25519PubKey([56; 32]); |
||||
let client_id_not_added = PubKey::Ed25519PubKey([57; 32]); |
||||
|
||||
account2.add_client(&client_id).unwrap(); |
||||
|
||||
assert!(account2.is_admin().unwrap()); |
||||
|
||||
account.has_client(&client_id).unwrap(); |
||||
assert!(account.has_client(&client_id_not_added).is_err()); |
||||
} |
||||
} |
@ -0,0 +1,103 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Broker Config, persisted to store
|
||||
|
||||
use p2p_repo::broker_store::BrokerStore; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_net::types::*; |
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::{from_slice, to_vec}; |
||||
|
||||
// TODO: versioning V0
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |
||||
pub enum ConfigMode { |
||||
Local, |
||||
Core, |
||||
} |
||||
|
||||
pub struct Config<'a> { |
||||
store: &'a dyn BrokerStore, |
||||
} |
||||
|
||||
impl<'a> Config<'a> { |
||||
const PREFIX: u8 = b"c"[0]; |
||||
|
||||
const KEY: [u8; 5] = *b"onfig"; |
||||
|
||||
// propertie's suffixes
|
||||
const MODE: u8 = b"m"[0]; |
||||
|
||||
const ALL_PROPERTIES: [u8; 1] = [Self::MODE]; |
||||
|
||||
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::MODE; |
||||
|
||||
pub fn open(store: &'a dyn BrokerStore) -> Result<Config<'a>, StorageError> { |
||||
let opening = Config { store }; |
||||
if !opening.exists() { |
||||
return Err(StorageError::NotFound); |
||||
} |
||||
Ok(opening) |
||||
} |
||||
pub fn get_or_create( |
||||
mode: &ConfigMode, |
||||
store: &'a dyn BrokerStore, |
||||
) -> Result<Config<'a>, StorageError> { |
||||
match Self::open(store) { |
||||
Err(e) => { |
||||
if e == StorageError::NotFound { |
||||
Self::create(mode, store) |
||||
} else { |
||||
Err(StorageError::BackendError) |
||||
} |
||||
} |
||||
Ok(p) => { |
||||
if &p.mode().unwrap() != mode { |
||||
return Err(StorageError::InvalidValue); |
||||
} |
||||
Ok(p) |
||||
} |
||||
} |
||||
} |
||||
pub fn create( |
||||
mode: &ConfigMode, |
||||
store: &'a dyn BrokerStore, |
||||
) -> Result<Config<'a>, StorageError> { |
||||
let acc = Config { store }; |
||||
if acc.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&Self::KEY)?, |
||||
Some(Self::MODE), |
||||
to_vec(&mode)?, |
||||
)?; |
||||
Ok(acc) |
||||
} |
||||
pub fn exists(&self) -> bool { |
||||
self.store |
||||
.get( |
||||
Self::PREFIX, |
||||
&to_vec(&Self::KEY).unwrap(), |
||||
Some(Self::SUFFIX_FOR_EXIST_CHECK), |
||||
) |
||||
.is_ok() |
||||
} |
||||
pub fn mode(&self) -> Result<ConfigMode, StorageError> { |
||||
match self |
||||
.store |
||||
.get(Self::PREFIX, &to_vec(&Self::KEY)?, Some(Self::MODE)) |
||||
{ |
||||
Ok(ver) => Ok(from_slice::<ConfigMode>(&ver)?), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,219 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Overlay
|
||||
|
||||
use p2p_repo::broker_store::BrokerStore; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_net::types::*; |
||||
use p2p_repo::utils::now_timestamp; |
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::{from_slice, to_vec}; |
||||
|
||||
// TODO: versioning V0
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |
||||
pub struct OverlayMeta { |
||||
pub users: u32, |
||||
pub last_used: Timestamp, |
||||
} |
||||
|
||||
pub struct Overlay<'a> { |
||||
/// Overlay ID
|
||||
id: OverlayId, |
||||
store: &'a dyn BrokerStore, |
||||
} |
||||
|
||||
impl<'a> Overlay<'a> { |
||||
const PREFIX: u8 = b"o"[0]; |
||||
|
||||
// propertie's suffixes
|
||||
const SECRET: u8 = b"s"[0]; |
||||
const PEER: u8 = b"p"[0]; |
||||
const TOPIC: u8 = b"t"[0]; |
||||
const META: u8 = b"m"[0]; |
||||
const REPO: u8 = b"r"[0]; |
||||
|
||||
const ALL_PROPERTIES: [u8; 5] = [ |
||||
Self::SECRET, |
||||
Self::PEER, |
||||
Self::TOPIC, |
||||
Self::META, |
||||
Self::REPO, |
||||
]; |
||||
|
||||
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::SECRET; |
||||
|
||||
pub fn open(id: &OverlayId, store: &'a dyn BrokerStore) -> Result<Overlay<'a>, StorageError> { |
||||
let opening = Overlay { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if !opening.exists() { |
||||
return Err(StorageError::NotFound); |
||||
} |
||||
Ok(opening) |
||||
} |
||||
pub fn create( |
||||
id: &OverlayId, |
||||
secret: &SymKey, |
||||
repo: Option<PubKey>, |
||||
store: &'a dyn BrokerStore, |
||||
) -> Result<Overlay<'a>, StorageError> { |
||||
let acc = Overlay { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if acc.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&id)?, |
||||
Some(Self::SECRET), |
||||
to_vec(&secret)?, |
||||
)?; |
||||
if repo.is_some() { |
||||
store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&id)?, |
||||
Some(Self::REPO), |
||||
to_vec(&repo.unwrap())?, |
||||
)?; |
||||
//TODO if failure, should remove the previously added SECRET property
|
||||
} |
||||
let meta = OverlayMeta { |
||||
users: 1, |
||||
last_used: now_timestamp(), |
||||
}; |
||||
store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&id)?, |
||||
Some(Self::META), |
||||
to_vec(&meta)?, |
||||
)?; |
||||
//TODO if failure, should remove the previously added SECRET and REPO properties
|
||||
Ok(acc) |
||||
} |
||||
pub fn exists(&self) -> bool { |
||||
self.store |
||||
.get( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id).unwrap(), |
||||
Some(Self::SUFFIX_FOR_EXIST_CHECK), |
||||
) |
||||
.is_ok() |
||||
} |
||||
pub fn id(&self) -> OverlayId { |
||||
self.id |
||||
} |
||||
pub fn add_peer(&self, peer: &PeerId) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::PEER), |
||||
to_vec(peer)?, |
||||
) |
||||
} |
||||
pub fn remove_peer(&self, peer: &PeerId) -> Result<(), StorageError> { |
||||
self.store.del_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::PEER), |
||||
to_vec(peer)?, |
||||
) |
||||
} |
||||
|
||||
pub fn has_peer(&self, peer: &PeerId) -> Result<(), StorageError> { |
||||
self.store.has_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::PEER), |
||||
to_vec(peer)?, |
||||
) |
||||
} |
||||
|
||||
pub fn add_topic(&self, topic: &TopicId) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::TOPIC), |
||||
to_vec(topic)?, |
||||
) |
||||
} |
||||
pub fn remove_topic(&self, topic: &TopicId) -> Result<(), StorageError> { |
||||
self.store.del_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::TOPIC), |
||||
to_vec(topic)?, |
||||
) |
||||
} |
||||
|
||||
pub fn has_topic(&self, topic: &TopicId) -> Result<(), StorageError> { |
||||
self.store.has_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::TOPIC), |
||||
to_vec(topic)?, |
||||
) |
||||
} |
||||
|
||||
pub fn secret(&self) -> Result<SymKey, StorageError> { |
||||
match self |
||||
.store |
||||
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::SECRET)) |
||||
{ |
||||
Ok(secret) => Ok(from_slice::<SymKey>(&secret)?), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
|
||||
pub fn metadata(&self) -> Result<OverlayMeta, StorageError> { |
||||
match self |
||||
.store |
||||
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::META)) |
||||
{ |
||||
Ok(meta) => Ok(from_slice::<OverlayMeta>(&meta)?), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
pub fn set_metadata(&self, meta: &OverlayMeta) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.replace( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::META), |
||||
to_vec(meta)?, |
||||
) |
||||
} |
||||
|
||||
pub fn repo(&self) -> Result<PubKey, StorageError> { |
||||
match self |
||||
.store |
||||
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::REPO)) |
||||
{ |
||||
Ok(repo) => Ok(from_slice::<PubKey>(&repo)?), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
|
||||
pub fn del(&self) -> Result<(), StorageError> { |
||||
self.store |
||||
.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES) |
||||
} |
||||
} |
@ -0,0 +1,163 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Peer
|
||||
|
||||
use p2p_repo::broker_store::BrokerStore; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_net::types::*; |
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::{from_slice, to_vec}; |
||||
|
||||
pub struct Peer<'a> { |
||||
/// Topic ID
|
||||
id: PeerId, |
||||
store: &'a dyn BrokerStore, |
||||
} |
||||
|
||||
impl<'a> Peer<'a> { |
||||
const PREFIX: u8 = b"p"[0]; |
||||
|
||||
// propertie's suffixes
|
||||
const VERSION: u8 = b"v"[0]; |
||||
const ADVERT: u8 = b"a"[0]; |
||||
|
||||
const ALL_PROPERTIES: [u8; 2] = [Self::VERSION, Self::ADVERT]; |
||||
|
||||
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::VERSION; |
||||
|
||||
pub fn open(id: &PeerId, store: &'a dyn BrokerStore) -> Result<Peer<'a>, StorageError> { |
||||
let opening = Peer { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if !opening.exists() { |
||||
return Err(StorageError::NotFound); |
||||
} |
||||
Ok(opening) |
||||
} |
||||
pub fn update_or_create( |
||||
advert: &PeerAdvert, |
||||
store: &'a dyn BrokerStore, |
||||
) -> Result<Peer<'a>, StorageError> { |
||||
let id = advert.peer(); |
||||
match Self::open(id, store) { |
||||
Err(e) => { |
||||
if e == StorageError::NotFound { |
||||
Self::create(advert, store) |
||||
} else { |
||||
Err(StorageError::BackendError) |
||||
} |
||||
} |
||||
Ok(p) => { |
||||
p.update_advert(advert)?; |
||||
Ok(p) |
||||
} |
||||
} |
||||
} |
||||
pub fn create( |
||||
advert: &PeerAdvert, |
||||
store: &'a dyn BrokerStore, |
||||
) -> Result<Peer<'a>, StorageError> { |
||||
let id = advert.peer(); |
||||
let acc = Peer { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if acc.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&id)?, |
||||
Some(Self::VERSION), |
||||
to_vec(&advert.version())?, |
||||
)?; |
||||
store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&id)?, |
||||
Some(Self::ADVERT), |
||||
to_vec(&advert)?, |
||||
)?; |
||||
Ok(acc) |
||||
} |
||||
pub fn exists(&self) -> bool { |
||||
self.store |
||||
.get( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id).unwrap(), |
||||
Some(Self::SUFFIX_FOR_EXIST_CHECK), |
||||
) |
||||
.is_ok() |
||||
} |
||||
pub fn id(&self) -> PeerId { |
||||
self.id |
||||
} |
||||
pub fn version(&self) -> Result<u32, StorageError> { |
||||
match self |
||||
.store |
||||
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::VERSION)) |
||||
{ |
||||
Ok(ver) => Ok(from_slice::<u32>(&ver)?), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
pub fn set_version(&self, version: u32) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.replace( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::VERSION), |
||||
to_vec(&version)?, |
||||
) |
||||
} |
||||
pub fn update_advert(&self, advert: &PeerAdvert) -> Result<(), StorageError> { |
||||
if advert.peer() != &self.id { |
||||
return Err(StorageError::InvalidValue); |
||||
} |
||||
let current_advert = self.advert().map_err(|e| StorageError::BackendError)?; |
||||
if current_advert.version() >= advert.version() { |
||||
return Ok(()); |
||||
} |
||||
self.store.replace( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::ADVERT), |
||||
to_vec(advert)?, |
||||
) |
||||
} |
||||
pub fn advert(&self) -> Result<PeerAdvert, StorageError> { |
||||
match self |
||||
.store |
||||
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::ADVERT)) |
||||
{ |
||||
Ok(advert) => Ok(from_slice::<PeerAdvert>(&advert)?), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
pub fn set_advert(&self, advert: &PeerAdvert) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.replace( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::ADVERT), |
||||
to_vec(advert)?, |
||||
) |
||||
} |
||||
|
||||
pub fn del(&self) -> Result<(), StorageError> { |
||||
self.store |
||||
.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES) |
||||
} |
||||
} |
@ -0,0 +1,103 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! RepoStore information about each RepoStore
|
||||
//! It contains the symKeys to open the RepoStores
|
||||
//! A repoStore is identified by its repo pubkey if in local mode
|
||||
//! In core mode, it is identified by the overlayid.
|
||||
|
||||
use p2p_repo::broker_store::BrokerStore; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_net::types::*; |
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::{from_slice, to_vec}; |
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] |
||||
pub enum RepoStoreId { |
||||
Overlay(OverlayId), |
||||
Repo(PubKey), |
||||
} |
||||
|
||||
impl From<RepoStoreId> for String { |
||||
fn from(id: RepoStoreId) -> Self { |
||||
hex::encode(to_vec(&id).unwrap()) |
||||
} |
||||
} |
||||
|
||||
pub struct RepoStoreInfo<'a> { |
||||
/// RepoStore ID
|
||||
id: RepoStoreId, |
||||
store: &'a dyn BrokerStore, |
||||
} |
||||
|
||||
impl<'a> RepoStoreInfo<'a> { |
||||
const PREFIX: u8 = b"r"[0]; |
||||
|
||||
// propertie's suffixes
|
||||
const KEY: u8 = b"k"[0]; |
||||
|
||||
const ALL_PROPERTIES: [u8; 1] = [Self::KEY]; |
||||
|
||||
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::KEY; |
||||
|
||||
pub fn open( |
||||
id: &RepoStoreId, |
||||
store: &'a dyn BrokerStore, |
||||
) -> Result<RepoStoreInfo<'a>, StorageError> { |
||||
let opening = RepoStoreInfo { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if !opening.exists() { |
||||
return Err(StorageError::NotFound); |
||||
} |
||||
Ok(opening) |
||||
} |
||||
pub fn create( |
||||
id: &RepoStoreId, |
||||
key: &SymKey, |
||||
store: &'a dyn BrokerStore, |
||||
) -> Result<RepoStoreInfo<'a>, StorageError> { |
||||
let acc = RepoStoreInfo { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if acc.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
store.put(Self::PREFIX, &to_vec(&id)?, Some(Self::KEY), to_vec(key)?)?; |
||||
Ok(acc) |
||||
} |
||||
pub fn exists(&self) -> bool { |
||||
self.store |
||||
.get( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id).unwrap(), |
||||
Some(Self::SUFFIX_FOR_EXIST_CHECK), |
||||
) |
||||
.is_ok() |
||||
} |
||||
pub fn id(&self) -> &RepoStoreId { |
||||
&self.id |
||||
} |
||||
pub fn key(&self) -> Result<SymKey, StorageError> { |
||||
match self |
||||
.store |
||||
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::KEY)) |
||||
{ |
||||
Ok(k) => Ok(from_slice::<SymKey>(&k)?), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
pub fn del(&self) -> Result<(), StorageError> { |
||||
self.store |
||||
.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES) |
||||
} |
||||
} |
@ -0,0 +1,136 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Topic
|
||||
|
||||
use p2p_repo::broker_store::BrokerStore; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_net::types::*; |
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::{from_slice, to_vec}; |
||||
|
||||
// TODO: versioning V0
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |
||||
pub struct TopicMeta { |
||||
pub users: u32, |
||||
} |
||||
|
||||
pub struct Topic<'a> { |
||||
/// Topic ID
|
||||
id: TopicId, |
||||
store: &'a dyn BrokerStore, |
||||
} |
||||
|
||||
impl<'a> Topic<'a> { |
||||
const PREFIX: u8 = b"t"[0]; |
||||
|
||||
// propertie's suffixes
|
||||
const ADVERT: u8 = b"a"[0]; |
||||
const HEAD: u8 = b"h"[0]; |
||||
const META: u8 = b"m"[0]; |
||||
|
||||
const ALL_PROPERTIES: [u8; 3] = [Self::ADVERT, Self::HEAD, Self::META]; |
||||
|
||||
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::META; |
||||
|
||||
pub fn open(id: &TopicId, store: &'a dyn BrokerStore) -> Result<Topic<'a>, StorageError> { |
||||
let opening = Topic { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if !opening.exists() { |
||||
return Err(StorageError::NotFound); |
||||
} |
||||
Ok(opening) |
||||
} |
||||
pub fn create(id: &TopicId, store: &'a dyn BrokerStore) -> Result<Topic<'a>, StorageError> { |
||||
let acc = Topic { |
||||
id: id.clone(), |
||||
store, |
||||
}; |
||||
if acc.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
let meta = TopicMeta { users: 0 }; |
||||
store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&id)?, |
||||
Some(Self::META), |
||||
to_vec(&meta)?, |
||||
)?; |
||||
Ok(acc) |
||||
} |
||||
pub fn exists(&self) -> bool { |
||||
self.store |
||||
.get( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id).unwrap(), |
||||
Some(Self::SUFFIX_FOR_EXIST_CHECK), |
||||
) |
||||
.is_ok() |
||||
} |
||||
pub fn id(&self) -> TopicId { |
||||
self.id |
||||
} |
||||
pub fn add_head(&self, head: &ObjectId) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.put( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::HEAD), |
||||
to_vec(head)?, |
||||
) |
||||
} |
||||
pub fn remove_head(&self, head: &ObjectId) -> Result<(), StorageError> { |
||||
self.store.del_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::HEAD), |
||||
to_vec(head)?, |
||||
) |
||||
} |
||||
|
||||
pub fn has_head(&self, head: &ObjectId) -> Result<(), StorageError> { |
||||
self.store.has_property_value( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::HEAD), |
||||
to_vec(head)?, |
||||
) |
||||
} |
||||
|
||||
pub fn metadata(&self) -> Result<TopicMeta, StorageError> { |
||||
match self |
||||
.store |
||||
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::META)) |
||||
{ |
||||
Ok(meta) => Ok(from_slice::<TopicMeta>(&meta)?), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
pub fn set_metadata(&self, meta: &TopicMeta) -> Result<(), StorageError> { |
||||
if !self.exists() { |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
self.store.replace( |
||||
Self::PREFIX, |
||||
&to_vec(&self.id)?, |
||||
Some(Self::META), |
||||
to_vec(meta)?, |
||||
) |
||||
} |
||||
|
||||
pub fn del(&self) -> Result<(), StorageError> { |
||||
self.store |
||||
.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES) |
||||
} |
||||
} |
@ -0,0 +1,148 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Connection to a Broker, can be local or remote.
|
||||
//! If remote, it will use a Stream and Sink of framed messages
|
||||
|
||||
use futures::{ |
||||
ready, |
||||
stream::Stream, |
||||
task::{Context, Poll}, |
||||
Future, |
||||
select, FutureExt, |
||||
}; |
||||
use futures::channel::mpsc; |
||||
use std::pin::Pin; |
||||
use std::{collections::HashSet, fmt::Debug}; |
||||
|
||||
use crate::server::BrokerServer; |
||||
use debug_print::*; |
||||
use futures::{pin_mut, stream, Sink, SinkExt, StreamExt}; |
||||
use p2p_repo::object::*; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
use p2p_net::errors::*; |
||||
use p2p_net::types::*; |
||||
use p2p_net::broker_connection::*; |
||||
use std::collections::HashMap; |
||||
|
||||
|
||||
pub struct BrokerConnectionLocal<'a> { |
||||
broker: &'a mut BrokerServer, |
||||
user: PubKey, |
||||
} |
||||
|
||||
#[async_trait::async_trait] |
||||
impl<'a> BrokerConnection for BrokerConnectionLocal<'a> { |
||||
type OC = BrokerConnectionLocal<'a>; |
||||
type BlockStream = async_channel::Receiver<Block>; |
||||
|
||||
async fn close(&mut self) {} |
||||
|
||||
async fn add_user( |
||||
&mut self, |
||||
user_id: PubKey, |
||||
admin_user_pk: PrivKey, |
||||
) -> Result<(), ProtocolError> { |
||||
let op_content = AddUserContentV0 { user: user_id }; |
||||
let sig = sign(admin_user_pk, self.user, &serde_bare::to_vec(&op_content)?)?; |
||||
|
||||
self.broker.add_user(self.user, user_id, sig) |
||||
} |
||||
|
||||
async fn process_overlay_request( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<(), ProtocolError> { |
||||
match request { |
||||
BrokerOverlayRequestContentV0::OverlayConnect(_) => { |
||||
self.broker.connect_overlay(self.user, overlay) |
||||
} |
||||
BrokerOverlayRequestContentV0::OverlayJoin(j) => { |
||||
self.broker |
||||
.join_overlay(self.user, overlay, j.repo_pubkey(), j.secret(), j.peers()) |
||||
} |
||||
BrokerOverlayRequestContentV0::ObjectPin(op) => { |
||||
self.broker.pin_object(self.user, overlay, op.id()) |
||||
} |
||||
BrokerOverlayRequestContentV0::ObjectUnpin(op) => { |
||||
self.broker.unpin_object(self.user, overlay, op.id()) |
||||
} |
||||
BrokerOverlayRequestContentV0::ObjectDel(op) => { |
||||
self.broker.del_object(self.user, overlay, op.id()) |
||||
} |
||||
BrokerOverlayRequestContentV0::BlockPut(b) => { |
||||
self.broker.put_block(self.user, overlay, b.block()) |
||||
} |
||||
_ => Err(ProtocolError::InvalidState), |
||||
} |
||||
} |
||||
|
||||
async fn process_overlay_request_objectid_response( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<ObjectId, ProtocolError> { |
||||
match request { |
||||
BrokerOverlayRequestContentV0::ObjectCopy(oc) => { |
||||
self.broker |
||||
.copy_object(self.user, overlay, oc.id(), oc.expiry()) |
||||
} |
||||
_ => Err(ProtocolError::InvalidState), |
||||
} |
||||
} |
||||
|
||||
async fn process_overlay_request_stream_response( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<Pin<Box<Self::BlockStream>>, ProtocolError> { |
||||
match request { |
||||
|
||||
BrokerOverlayRequestContentV0::BlockGet(b) => self |
||||
.broker |
||||
.get_block(self.user, overlay, b.id(), b.include_children(), b.topic()) |
||||
.map(|r| Box::pin(r)), |
||||
BrokerOverlayRequestContentV0::BranchSyncReq(b) => self |
||||
.broker |
||||
.sync_branch( |
||||
self.user, |
||||
&overlay, |
||||
b.heads(), |
||||
b.known_heads(), |
||||
b.known_commits(), |
||||
) |
||||
.map(|r| Box::pin(r)), |
||||
_ => Err(ProtocolError::InvalidState), |
||||
} |
||||
} |
||||
|
||||
async fn del_user(&mut self, user_id: PubKey, admin_user_pk: PrivKey) {} |
||||
|
||||
async fn add_client(&mut self, user_id: PubKey, admin_user_pk: PrivKey) {} |
||||
|
||||
async fn del_client(&mut self, user_id: PubKey, admin_user_pk: PrivKey) {} |
||||
|
||||
async fn overlay_connect( |
||||
&mut self, |
||||
repo_link: &RepoLink, |
||||
public: bool, |
||||
) -> Result<OverlayConnectionClient<BrokerConnectionLocal<'a>>, ProtocolError> { |
||||
let overlay = self.process_overlay_connect(repo_link, public).await?; |
||||
Ok(OverlayConnectionClient::create(self, overlay, repo_link.clone())) |
||||
} |
||||
} |
||||
|
||||
impl<'a> BrokerConnectionLocal<'a> { |
||||
pub fn new(broker: &'a mut BrokerServer, user: PubKey) -> BrokerConnectionLocal<'a> { |
||||
BrokerConnectionLocal { broker, user } |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
pub mod broker_store_account; |
||||
|
||||
pub mod broker_store_config; |
||||
|
||||
pub mod broker_store_overlay; |
||||
|
||||
pub mod broker_store_peer; |
||||
|
||||
pub mod broker_store_repostoreinfo; |
||||
|
||||
pub mod broker_store_topic; |
||||
|
||||
pub mod connection_local; |
||||
|
||||
pub mod server; |
||||
|
||||
pub mod server_ws; |
||||
|
||||
pub mod auth; |
@ -0,0 +1,891 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! A Broker server
|
||||
|
||||
use std::collections::HashMap; |
||||
use std::collections::HashSet; |
||||
use std::pin::Pin; |
||||
use std::sync::Arc; |
||||
use std::sync::RwLock; |
||||
|
||||
use crate::broker_store_account::Account; |
||||
use crate::auth::*; |
||||
use crate::broker_store_config::Config; |
||||
use crate::broker_store_config::ConfigMode; |
||||
use crate::connection_local::BrokerConnectionLocal; |
||||
use crate::broker_store_overlay::Overlay; |
||||
use crate::broker_store_peer::Peer; |
||||
use crate::broker_store_repostoreinfo::RepoStoreId; |
||||
use crate::broker_store_repostoreinfo::RepoStoreInfo; |
||||
use async_std::task; |
||||
use debug_print::*; |
||||
use futures::future::BoxFuture; |
||||
use futures::future::OptionFuture; |
||||
use futures::FutureExt; |
||||
use futures::Stream; |
||||
use p2p_repo::object::Object; |
||||
use p2p_repo::store::RepoStore; |
||||
use p2p_repo::store::StorageError; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
use p2p_net::errors::*; |
||||
use p2p_net::types::*; |
||||
use p2p_stores_lmdb::broker_store::LmdbBrokerStore; |
||||
use p2p_stores_lmdb::repo_store::LmdbRepoStore; |
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)] |
||||
pub enum BrokerError { |
||||
CannotStart, |
||||
MismatchedMode, |
||||
OverlayNotFound, |
||||
} |
||||
|
||||
impl From<BrokerError> for ProtocolError { |
||||
fn from(e: BrokerError) -> Self { |
||||
match e { |
||||
BrokerError::CannotStart => ProtocolError::OverlayNotFound, |
||||
BrokerError::OverlayNotFound => ProtocolError::OverlayNotFound, |
||||
_ => ProtocolError::BrokerError, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<p2p_repo::store::StorageError> for BrokerError { |
||||
fn from(e: p2p_repo::store::StorageError) -> Self { |
||||
match e { |
||||
p2p_repo::store::StorageError::InvalidValue => BrokerError::MismatchedMode, |
||||
_ => BrokerError::CannotStart, |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
enum ProtocolType { |
||||
Start, |
||||
Auth, |
||||
Broker, |
||||
Ext, |
||||
P2P, |
||||
} |
||||
|
||||
pub struct ProtocolHandler { |
||||
broker: Arc<BrokerServer>, |
||||
protocol: ProtocolType, |
||||
auth_protocol: Option<AuthProtocolHandler>, |
||||
broker_protocol: Option<BrokerProtocolHandler>, |
||||
ext_protocol: Option<ExtProtocolHandler>, |
||||
r: Option<async_channel::Receiver<Vec<u8>>>, |
||||
s: async_channel::Sender<Vec<u8>>, |
||||
} |
||||
|
||||
impl ProtocolHandler { |
||||
pub fn async_frames_receiver(&mut self) -> async_channel::Receiver<Vec<u8>> { |
||||
self.r.take().unwrap() |
||||
} |
||||
|
||||
/// Handle incoming message
|
||||
pub async fn handle_incoming( |
||||
&mut self, |
||||
frame: Vec<u8>, |
||||
) -> ( |
||||
Result<Vec<u8>, ProtocolError>, |
||||
OptionFuture<BoxFuture<'static, u16>>, |
||||
) { |
||||
//debug_println!("SERVER PROTOCOL {:?}", &self.protocol);
|
||||
match &self.protocol { |
||||
ProtocolType::Start => { |
||||
let message = serde_bare::from_slice::<StartProtocol>(&frame); |
||||
match message { |
||||
Ok(StartProtocol::Auth(b)) => { |
||||
self.protocol = ProtocolType::Auth; |
||||
self.auth_protocol = Some(AuthProtocolHandler::new()); |
||||
return ( |
||||
self.auth_protocol.as_mut().unwrap().handle_init(b), |
||||
OptionFuture::from(None), |
||||
); |
||||
} |
||||
Ok(StartProtocol::Ext(ext)) => { |
||||
self.protocol = ProtocolType::Ext; |
||||
self.ext_protocol = Some(ExtProtocolHandler {}); |
||||
let reply = self.ext_protocol.as_ref().unwrap().handle_incoming(ext); |
||||
return ( |
||||
Ok(serde_bare::to_vec(&reply).unwrap()), |
||||
OptionFuture::from(None), |
||||
); |
||||
} |
||||
Err(e) => { |
||||
return (Err(ProtocolError::SerializationError),OptionFuture::from(None)) |
||||
} |
||||
} |
||||
} |
||||
ProtocolType::Auth => { |
||||
let res = self.auth_protocol.as_mut().unwrap().handle_incoming(frame); |
||||
match res.1.await { |
||||
None => { |
||||
// we switch to Broker protocol
|
||||
self.protocol = ProtocolType::Broker; |
||||
self.broker_protocol = Some(BrokerProtocolHandler { |
||||
user: self.auth_protocol.as_ref().unwrap().get_user().unwrap(), |
||||
broker: Arc::clone(&self.broker), |
||||
async_frames_sender: self.s.clone(), |
||||
}); |
||||
self.auth_protocol = None; |
||||
(res.0, OptionFuture::from(None)) |
||||
} |
||||
Some(e) => (res.0, OptionFuture::from(Some(async move { e }.boxed()))), |
||||
} |
||||
} |
||||
ProtocolType::Broker => { |
||||
let message = serde_bare::from_slice::<BrokerMessage>(&frame); |
||||
match (message) { |
||||
Ok(message) => { |
||||
let reply = self |
||||
.broker_protocol |
||||
.as_ref() |
||||
.unwrap() |
||||
.handle_incoming(message) |
||||
.await; |
||||
(Ok(serde_bare::to_vec(&reply.0).unwrap()), reply.1) |
||||
} |
||||
Err(e_) => { |
||||
(Err(ProtocolError::SerializationError),OptionFuture::from(None)) |
||||
} |
||||
} |
||||
} |
||||
ProtocolType::Ext => { |
||||
// Ext protocol is not accepting 2 extrequest in the same connection.
|
||||
// closing the connection
|
||||
(Err(ProtocolError::InvalidState), OptionFuture::from(None)) |
||||
} |
||||
ProtocolType::P2P => { |
||||
unimplemented!() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub struct ExtProtocolHandler {} |
||||
|
||||
impl ExtProtocolHandler { |
||||
pub fn handle_incoming(&self, msg: ExtRequest) -> ExtResponse { |
||||
unimplemented!() |
||||
} |
||||
} |
||||
|
||||
pub struct BrokerProtocolHandler { |
||||
broker: Arc<BrokerServer>, |
||||
user: PubKey, |
||||
async_frames_sender: async_channel::Sender<Vec<u8>>, |
||||
} |
||||
use std::{thread, time}; |
||||
|
||||
impl BrokerProtocolHandler { |
||||
fn prepare_reply_broker_message( |
||||
res: Result<(), ProtocolError>, |
||||
id: u64, |
||||
padding_size: usize, |
||||
) -> BrokerMessage { |
||||
let result = match res { |
||||
Ok(_) => 0, |
||||
Err(e) => e.into(), |
||||
}; |
||||
let msg = BrokerMessage::V0(BrokerMessageV0 { |
||||
padding: vec![0; padding_size], |
||||
content: BrokerMessageContentV0::BrokerResponse(BrokerResponse::V0(BrokerResponseV0 { |
||||
id, |
||||
result, |
||||
})), |
||||
}); |
||||
msg |
||||
} |
||||
|
||||
fn prepare_reply_broker_overlay_message( |
||||
res: Result<(), ProtocolError>, |
||||
id: u64, |
||||
overlay: OverlayId, |
||||
block: Option<Block>, |
||||
padding_size: usize, |
||||
) -> BrokerMessage { |
||||
let result = match res { |
||||
Ok(_) => 0, |
||||
Err(e) => e.into(), |
||||
}; |
||||
let content = match block { |
||||
Some(b) => Some(BrokerOverlayResponseContentV0::Block(b)), |
||||
None => None, |
||||
}; |
||||
let msg = BrokerMessage::V0(BrokerMessageV0 { |
||||
padding: vec![0; padding_size], |
||||
content: BrokerMessageContentV0::BrokerOverlayMessage(BrokerOverlayMessage::V0( |
||||
BrokerOverlayMessageV0 { |
||||
overlay, |
||||
content: BrokerOverlayMessageContentV0::BrokerOverlayResponse( |
||||
BrokerOverlayResponse::V0(BrokerOverlayResponseV0 { |
||||
id, |
||||
result, |
||||
content, |
||||
}), |
||||
), |
||||
}, |
||||
)), |
||||
}); |
||||
msg |
||||
} |
||||
|
||||
fn prepare_reply_broker_overlay_message_stream( |
||||
res: Result<Block, ProtocolError>, |
||||
id: u64, |
||||
overlay: OverlayId, |
||||
padding_size: usize, |
||||
) -> BrokerMessage { |
||||
let result: u16 = match &res { |
||||
Ok(r) => ProtocolError::PartialContent.into(), |
||||
Err(e) => (*e).clone().into(), |
||||
}; |
||||
let content = match res { |
||||
Ok(r) => Some(BrokerOverlayResponseContentV0::Block(r)), |
||||
Err(_) => None, |
||||
}; |
||||
let msg = BrokerMessage::V0(BrokerMessageV0 { |
||||
padding: vec![0; padding_size], |
||||
content: BrokerMessageContentV0::BrokerOverlayMessage(BrokerOverlayMessage::V0( |
||||
BrokerOverlayMessageV0 { |
||||
overlay, |
||||
content: BrokerOverlayMessageContentV0::BrokerOverlayResponse( |
||||
BrokerOverlayResponse::V0(BrokerOverlayResponseV0 { |
||||
id, |
||||
result, |
||||
content, |
||||
}), |
||||
), |
||||
}, |
||||
)), |
||||
}); |
||||
msg |
||||
} |
||||
|
||||
async fn send_block_stream_response_to_client( |
||||
&self, |
||||
res: Result<async_channel::Receiver<Block>, ProtocolError>, |
||||
id: u64, |
||||
overlay: OverlayId, |
||||
padding_size: usize, |
||||
) -> (BrokerMessage, OptionFuture<BoxFuture<'static, u16>>) { |
||||
// return an error or the first block, and setup a spawner for the remaining blocks to be sent.
|
||||
let one_reply: ( |
||||
Result<Block, ProtocolError>, |
||||
OptionFuture<BoxFuture<'static, u16>>, |
||||
) = match res { |
||||
Err(e) => (Err(e), OptionFuture::from(None)), |
||||
Ok(stream) => { |
||||
let one = stream |
||||
.recv_blocking() |
||||
.map_err(|e| ProtocolError::EndOfStream); |
||||
|
||||
if one.is_ok() { |
||||
let sender = self.async_frames_sender.clone(); |
||||
let a = OptionFuture::from(Some( |
||||
async move { |
||||
while let Ok(next) = stream.recv().await { |
||||
let msg = Self::prepare_reply_broker_overlay_message_stream( |
||||
Ok(next), |
||||
id, |
||||
overlay, |
||||
padding_size, |
||||
); |
||||
let res = sender.send(serde_bare::to_vec(&msg).unwrap()).await; |
||||
if res.is_err() { |
||||
break; |
||||
} |
||||
} |
||||
// sending end of stream
|
||||
let msg = Self::prepare_reply_broker_overlay_message_stream( |
||||
Err(ProtocolError::EndOfStream), |
||||
id, |
||||
overlay, |
||||
padding_size, |
||||
); |
||||
let _ = sender.send(serde_bare::to_vec(&msg).unwrap()).await; |
||||
0 |
||||
} |
||||
.boxed(), |
||||
)); |
||||
(one, a) |
||||
} else { |
||||
(one, OptionFuture::from(None)) |
||||
} |
||||
} |
||||
}; |
||||
return ( |
||||
Self::prepare_reply_broker_overlay_message_stream( |
||||
one_reply.0, |
||||
id, |
||||
overlay, |
||||
padding_size, |
||||
), |
||||
one_reply.1, |
||||
); |
||||
} |
||||
|
||||
pub async fn handle_incoming( |
||||
&self, |
||||
msg: BrokerMessage, |
||||
) -> (BrokerMessage, OptionFuture<BoxFuture<'static, u16>>) { |
||||
|
||||
let padding_size = 20; // TODO randomize, if config of server contains padding_max
|
||||
|
||||
let id = msg.id(); |
||||
let content = msg.content(); |
||||
match content { |
||||
BrokerMessageContentV0::BrokerRequest(req) => ( |
||||
Self::prepare_reply_broker_message( |
||||
match req.content_v0() { |
||||
BrokerRequestContentV0::AddUser(cmd) => { |
||||
self.broker.add_user(self.user, cmd.user(), cmd.sig()) |
||||
} |
||||
BrokerRequestContentV0::DelUser(cmd) => { |
||||
self.broker.del_user(self.user, cmd.user(), cmd.sig()) |
||||
} |
||||
BrokerRequestContentV0::AddClient(cmd) => { |
||||
self.broker.add_client(self.user, cmd.client(), cmd.sig()) |
||||
} |
||||
BrokerRequestContentV0::DelClient(cmd) => { |
||||
self.broker.del_client(self.user, cmd.client(), cmd.sig()) |
||||
} |
||||
}, |
||||
id, |
||||
padding_size, |
||||
), |
||||
OptionFuture::from(None), |
||||
), |
||||
BrokerMessageContentV0::BrokerResponse(res) => ( |
||||
Self::prepare_reply_broker_message( |
||||
Err(ProtocolError::InvalidState), |
||||
id, |
||||
padding_size, |
||||
), |
||||
OptionFuture::from(None), |
||||
), |
||||
BrokerMessageContentV0::BrokerOverlayMessage(omsg) => { |
||||
let overlay = omsg.overlay_id(); |
||||
let block = None; |
||||
let mut res = Err(ProtocolError::InvalidState); |
||||
|
||||
if omsg.is_request() { |
||||
match omsg.overlay_request().content_v0() { |
||||
BrokerOverlayRequestContentV0::OverlayConnect(_) => { |
||||
res = self.broker.connect_overlay(self.user, overlay) |
||||
} |
||||
BrokerOverlayRequestContentV0::OverlayJoin(j) => { |
||||
res = self.broker.join_overlay( |
||||
self.user, |
||||
overlay, |
||||
j.repo_pubkey(), |
||||
j.secret(), |
||||
j.peers(), |
||||
) |
||||
} |
||||
BrokerOverlayRequestContentV0::ObjectDel(op) => { |
||||
res = self.broker.del_object(self.user, overlay, op.id()) |
||||
} |
||||
BrokerOverlayRequestContentV0::ObjectPin(op) => { |
||||
res = self.broker.pin_object(self.user, overlay, op.id()) |
||||
} |
||||
BrokerOverlayRequestContentV0::ObjectUnpin(op) => { |
||||
res = self.broker.unpin_object(self.user, overlay, op.id()) |
||||
} |
||||
BrokerOverlayRequestContentV0::BlockPut(b) => { |
||||
res = self.broker.put_block(self.user, overlay, b.block()) |
||||
} |
||||
BrokerOverlayRequestContentV0::BranchSyncReq(b) => { |
||||
let res = self.broker.sync_branch( |
||||
self.user, |
||||
&overlay, |
||||
b.heads(), |
||||
b.known_heads(), |
||||
b.known_commits(), |
||||
); |
||||
return self |
||||
.send_block_stream_response_to_client( |
||||
res, |
||||
id, |
||||
overlay, |
||||
padding_size, |
||||
) |
||||
.await; |
||||
} |
||||
BrokerOverlayRequestContentV0::BlockGet(b) => { |
||||
let res = self.broker.get_block( |
||||
self.user, |
||||
overlay, |
||||
b.id(), |
||||
b.include_children(), |
||||
b.topic(), |
||||
); |
||||
return self |
||||
.send_block_stream_response_to_client( |
||||
res, |
||||
id, |
||||
overlay, |
||||
padding_size, |
||||
) |
||||
.await; |
||||
} |
||||
_ => {} |
||||
} |
||||
} |
||||
|
||||
( |
||||
Self::prepare_reply_broker_overlay_message( |
||||
res, |
||||
id, |
||||
overlay, |
||||
block, |
||||
padding_size, |
||||
), |
||||
OptionFuture::from(None), |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
const REPO_STORES_SUBDIR: &str = "repos"; |
||||
|
||||
pub struct BrokerServer { |
||||
store: LmdbBrokerStore, |
||||
mode: ConfigMode, |
||||
repo_stores: Arc<RwLock<HashMap<RepoStoreId, LmdbRepoStore>>>, |
||||
// only used in ConfigMode::Local
|
||||
// try to change it to this version below in order to avoid double hashmap lookup in local mode. but hard to do...
|
||||
//overlayid_to_repostore: HashMap<RepoStoreId, &'a LmdbRepoStore>,
|
||||
overlayid_to_repostore: Arc<RwLock<HashMap<OverlayId, RepoStoreId>>>, |
||||
} |
||||
|
||||
impl BrokerServer { |
||||
pub fn new(store: LmdbBrokerStore, mode: ConfigMode) -> Result<BrokerServer, BrokerError> { |
||||
let mut configmode: ConfigMode; |
||||
{ |
||||
let config = Config::get_or_create(&mode, &store)?; |
||||
configmode = config.mode()?; |
||||
} |
||||
Ok(BrokerServer { |
||||
store, |
||||
mode: configmode, |
||||
repo_stores: Arc::new(RwLock::new(HashMap::new())), |
||||
overlayid_to_repostore: Arc::new(RwLock::new(HashMap::new())), |
||||
}) |
||||
} |
||||
|
||||
fn open_or_create_repostore<F, R>( |
||||
&self, |
||||
repostore_id: RepoStoreId, |
||||
f: F, |
||||
) -> Result<R, ProtocolError> |
||||
where |
||||
F: FnOnce(&LmdbRepoStore) -> Result<R, ProtocolError>, |
||||
{ |
||||
// first let's find it in the BrokerStore.repostoreinfo table in order to get the encryption key
|
||||
let info = RepoStoreInfo::open(&repostore_id, &self.store) |
||||
.map_err(|e| BrokerError::OverlayNotFound)?; |
||||
let key = info.key()?; |
||||
let mut path = self.store.path(); |
||||
path.push(REPO_STORES_SUBDIR); |
||||
path.push::<String>(repostore_id.clone().into()); |
||||
std::fs::create_dir_all(path.clone()).map_err(|_e| ProtocolError::WriteError )?; |
||||
println!("path for repo store: {}", path.to_str().unwrap()); |
||||
let repo = LmdbRepoStore::open(&path, *key.slice()); |
||||
let mut writer = self.repo_stores.write().expect("write repo_store hashmap"); |
||||
writer.insert(repostore_id.clone(), repo); |
||||
|
||||
f(writer.get(&repostore_id).unwrap()) |
||||
} |
||||
|
||||
fn get_repostore_from_overlay_id<F, R>( |
||||
&self, |
||||
overlay_id: &OverlayId, |
||||
f: F, |
||||
) -> Result<R, ProtocolError> |
||||
where |
||||
F: FnOnce(&LmdbRepoStore) -> Result<R, ProtocolError>, |
||||
{ |
||||
if self.mode == ConfigMode::Core { |
||||
let repostore_id = RepoStoreId::Overlay(*overlay_id); |
||||
let reader = self.repo_stores.read().expect("read repo_store hashmap"); |
||||
let rep = reader.get(&repostore_id); |
||||
match rep { |
||||
Some(repo) => return f(repo), |
||||
None => { |
||||
// we need to open/create it
|
||||
// TODO: last_access
|
||||
return self.open_or_create_repostore(repostore_id, |repo| f(repo)); |
||||
} |
||||
} |
||||
} else { |
||||
// it is ConfigMode::Local
|
||||
{ |
||||
let reader = self |
||||
.overlayid_to_repostore |
||||
.read() |
||||
.expect("read overlayid_to_repostore hashmap"); |
||||
match reader.get(&overlay_id) { |
||||
Some(repostoreid) => { |
||||
let reader = self.repo_stores.read().expect("read repo_store hashmap"); |
||||
match reader.get(repostoreid) { |
||||
Some(repo) => return f(repo), |
||||
None => return Err(ProtocolError::BrokerError), |
||||
} |
||||
} |
||||
None => {} |
||||
}; |
||||
} |
||||
|
||||
// we need to open/create it
|
||||
// first let's find it in the BrokerStore.overlay table to retrieve its repo_pubkey
|
||||
debug_println!("searching for overlayId {}", overlay_id); |
||||
let overlay = Overlay::open(overlay_id, &self.store)?; |
||||
debug_println!("found overlayId {}", overlay_id); |
||||
let repo_id = overlay.repo()?; |
||||
let repostore_id = RepoStoreId::Repo(repo_id); |
||||
let mut writer = self |
||||
.overlayid_to_repostore |
||||
.write() |
||||
.expect("write overlayid_to_repostore hashmap"); |
||||
writer.insert(*overlay_id, repostore_id.clone()); |
||||
// now opening/creating the RepoStore
|
||||
// TODO: last_access
|
||||
return self.open_or_create_repostore(repostore_id, |repo| f(repo)); |
||||
} |
||||
} |
||||
|
||||
pub fn local_connection(&mut self, user: PubKey) -> BrokerConnectionLocal { |
||||
BrokerConnectionLocal::new(self, user) |
||||
} |
||||
|
||||
pub fn protocol_handler(self: Arc<Self>) -> ProtocolHandler { |
||||
let (s, r) = async_channel::unbounded::<Vec<u8>>(); |
||||
return ProtocolHandler { |
||||
broker: Arc::clone(&self), |
||||
protocol: ProtocolType::Start, |
||||
auth_protocol: None, |
||||
broker_protocol: None, |
||||
ext_protocol: None, |
||||
r: Some(r), |
||||
s, |
||||
}; |
||||
} |
||||
|
||||
pub fn add_user( |
||||
&self, |
||||
admin_user: PubKey, |
||||
user_id: PubKey, |
||||
sig: Sig, |
||||
) -> Result<(), ProtocolError> { |
||||
debug_println!("ADDING USER {}", user_id); |
||||
// TODO add is_admin boolean
|
||||
// TODO check that admin_user is indeed an admin
|
||||
|
||||
// verify signature
|
||||
let op_content = AddUserContentV0 { user: user_id }; |
||||
let _ = verify(&serde_bare::to_vec(&op_content).unwrap(), sig, admin_user)?; |
||||
|
||||
// check user_id is not already present
|
||||
let account = Account::open(&user_id, &self.store); |
||||
if account.is_ok() { |
||||
Err(ProtocolError::UserAlreadyExists) |
||||
} |
||||
// if not, add to store
|
||||
else { |
||||
let _ = Account::create(&user_id, false, &self.store)?; |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
pub fn del_user( |
||||
&self, |
||||
admin_user: PubKey, |
||||
user_id: PubKey, |
||||
sig: Sig, |
||||
) -> Result<(), ProtocolError> { |
||||
// TODO implement del_user
|
||||
Ok(()) |
||||
} |
||||
pub fn add_client( |
||||
&self, |
||||
user: PubKey, |
||||
client_id: PubKey, |
||||
sig: Sig, |
||||
) -> Result<(), ProtocolError> { |
||||
// TODO implement add_client
|
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn del_client( |
||||
&self, |
||||
user: PubKey, |
||||
client_id: PubKey, |
||||
sig: Sig, |
||||
) -> Result<(), ProtocolError> { |
||||
// TODO implement del_client
|
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn connect_overlay(&self, user: PubKey, overlay: OverlayId) -> Result<(), ProtocolError> { |
||||
// TODO check that the broker has already joined this overlay. if not, send OverlayNotJoined
|
||||
Err(ProtocolError::OverlayNotJoined) |
||||
} |
||||
|
||||
pub fn del_object( |
||||
&self, |
||||
user: PubKey, |
||||
overlay: Digest, |
||||
id: ObjectId, |
||||
) -> Result<(), ProtocolError> { |
||||
self.get_repostore_from_overlay_id(&overlay, |store| { |
||||
// TODO, only admin users can delete on a store on this broker
|
||||
let obj = Object::load(id, None, store); |
||||
if obj.is_err() { |
||||
return Err(ProtocolError::NotFound); |
||||
} |
||||
let o = obj.ok().unwrap(); |
||||
let mut deduplicated: HashSet<ObjectId> = HashSet::new(); |
||||
for block in o.blocks() { |
||||
let id = block.id(); |
||||
if deduplicated.get(&id).is_none() { |
||||
store.del(&id)?; |
||||
deduplicated.insert(id); |
||||
} |
||||
} |
||||
Ok(()) |
||||
}) |
||||
} |
||||
|
||||
pub fn pin_object( |
||||
&self, |
||||
user: PubKey, |
||||
overlay: OverlayId, |
||||
id: ObjectId, |
||||
) -> Result<(), ProtocolError> { |
||||
self.get_repostore_from_overlay_id(&overlay, |store| { |
||||
// TODO, store the user who pins, and manage reference counting on how many users pin/unpin
|
||||
let obj = Object::load(id, None, store); |
||||
if obj.is_err() { |
||||
return Err(ProtocolError::NotFound); |
||||
} |
||||
let o = obj.ok().unwrap(); |
||||
let mut deduplicated: HashSet<ObjectId> = HashSet::new(); |
||||
for block in o.blocks() { |
||||
let id = block.id(); |
||||
if deduplicated.get(&id).is_none() { |
||||
store.pin(&id)?; |
||||
deduplicated.insert(id); |
||||
} |
||||
} |
||||
Ok(()) |
||||
}) |
||||
} |
||||
|
||||
pub fn unpin_object( |
||||
&self, |
||||
user: PubKey, |
||||
overlay: OverlayId, |
||||
id: ObjectId, |
||||
) -> Result<(), ProtocolError> { |
||||
self.get_repostore_from_overlay_id(&overlay, |store| { |
||||
// TODO, store the user who pins, and manage reference counting on how many users pin/unpin
|
||||
let obj = Object::load(id, None, store); |
||||
if obj.is_err() { |
||||
return Err(ProtocolError::NotFound); |
||||
} |
||||
let o = obj.ok().unwrap(); |
||||
let mut deduplicated: HashSet<ObjectId> = HashSet::new(); |
||||
for block in o.blocks() { |
||||
let id = block.id(); |
||||
if deduplicated.get(&id).is_none() { |
||||
store.unpin(&id)?; |
||||
deduplicated.insert(id); |
||||
} |
||||
} |
||||
Ok(()) |
||||
}) |
||||
} |
||||
|
||||
pub fn copy_object( |
||||
&self, |
||||
user: PubKey, |
||||
overlay: OverlayId, |
||||
id: ObjectId, |
||||
expiry: Option<Timestamp>, |
||||
) -> Result<ObjectId, ProtocolError> { |
||||
// self.get_repostore_from_overlay_id(&overlay, |store| {
|
||||
// //let obj = Object::from_store(id, None, store);
|
||||
// //Ok(Object::copy(id, expiry, store)?)
|
||||
// });
|
||||
todo!(); |
||||
} |
||||
|
||||
pub fn put_block( |
||||
&self, |
||||
user: PubKey, |
||||
overlay: OverlayId, |
||||
block: &Block, |
||||
) -> Result<(), ProtocolError> { |
||||
self.get_repostore_from_overlay_id(&overlay, |store| { |
||||
let _ = store.put(block)?; |
||||
Ok(()) |
||||
}) |
||||
} |
||||
|
||||
pub fn get_block( |
||||
&self, |
||||
user: PubKey, |
||||
overlay: OverlayId, |
||||
id: BlockId, |
||||
include_children: bool, |
||||
topic: Option<PubKey>, |
||||
) -> Result<async_channel::Receiver<Block>, ProtocolError> { |
||||
self.get_repostore_from_overlay_id(&overlay, |store| { |
||||
let (s, r) = async_channel::unbounded::<Block>(); |
||||
if !include_children { |
||||
let block = store.get(&id)?; |
||||
s.send_blocking(block) |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
Ok(r) |
||||
} else { |
||||
let obj = Object::load(id, None, store); |
||||
// TODO return partial blocks when some are missing ?
|
||||
if obj.is_err() { |
||||
//&& obj.err().unwrap().len() == 1 && obj.err().unwrap()[0] == id {
|
||||
return Err(ProtocolError::NotFound); |
||||
} |
||||
// TODO use a task to send non blocking (streaming)
|
||||
let o = obj.ok().unwrap(); |
||||
//debug_println!("{} BLOCKS ", o.blocks().len());
|
||||
let mut deduplicated: HashSet<BlockId> = HashSet::new(); |
||||
for block in o.blocks() { |
||||
let id = block.id(); |
||||
if deduplicated.get(&id).is_none() { |
||||
s.send_blocking(block.clone()) |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
deduplicated.insert(id); |
||||
} |
||||
} |
||||
Ok(r) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
pub fn sync_branch( |
||||
&self, |
||||
user: PubKey, |
||||
overlay: &OverlayId, |
||||
heads: &Vec<ObjectId>, |
||||
known_heads: &Vec<ObjectId>, |
||||
known_commits: &BloomFilter, |
||||
) -> Result<async_channel::Receiver<Block>, ProtocolError> { |
||||
//debug_println!("heads {:?}", heads);
|
||||
//debug_println!("known_heads {:?}", known_heads);
|
||||
//debug_println!("known_commits {:?}", known_commits);
|
||||
|
||||
self.get_repostore_from_overlay_id(&overlay, |store| { |
||||
let (s, r) = async_channel::unbounded::<Block>(); |
||||
|
||||
let res = Branch::sync_req(heads, known_heads, known_commits, store) |
||||
.map_err(|e| ProtocolError::ObjectParseError)?; |
||||
|
||||
// todo, use a task to send non blocking (streaming)
|
||||
debug_println!("SYNCING {} COMMITS", res.len()); |
||||
|
||||
let mut deduplicated: HashSet<BlockId> = HashSet::new(); |
||||
|
||||
for objectid in res { |
||||
let object = Object::load(objectid, None, store)?; |
||||
|
||||
for block in object.blocks() { |
||||
let id = block.id(); |
||||
if deduplicated.get(&id).is_none() { |
||||
s.send_blocking(block.clone()) |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
deduplicated.insert(id); |
||||
} |
||||
} |
||||
} |
||||
Ok(r) |
||||
}) |
||||
} |
||||
|
||||
fn compute_repostore_id(&self, overlay: OverlayId, repo_id: Option<PubKey>) -> RepoStoreId { |
||||
match self.mode { |
||||
ConfigMode::Core => RepoStoreId::Overlay(overlay), |
||||
ConfigMode::Local => RepoStoreId::Repo(repo_id.unwrap()), |
||||
} |
||||
} |
||||
|
||||
pub fn join_overlay( |
||||
&self, |
||||
user: PubKey, |
||||
overlay_id: OverlayId, |
||||
repo_id: Option<PubKey>, |
||||
secret: SymKey, |
||||
peers: &Vec<PeerAdvert>, |
||||
) -> Result<(), ProtocolError> { |
||||
// check if this overlay already exists
|
||||
//debug_println!("SEARCHING OVERLAY");
|
||||
let overlay_res = Overlay::open(&overlay_id, &self.store); |
||||
let overlay = match overlay_res { |
||||
Err(StorageError::NotFound) => { |
||||
// we have to add it
|
||||
if self.mode == ConfigMode::Local && repo_id.is_none() { |
||||
return Err(ProtocolError::RepoIdRequired); |
||||
} |
||||
let over = Overlay::create( |
||||
&overlay_id, |
||||
&secret, |
||||
if self.mode == ConfigMode::Local { |
||||
repo_id |
||||
} else { |
||||
None |
||||
}, |
||||
&self.store, |
||||
)?; |
||||
// we need to add an encryption key for the repostore.
|
||||
let mut random_buf = [0u8; 32]; |
||||
getrandom::getrandom(&mut random_buf).unwrap(); |
||||
let key = SymKey::ChaCha20Key(random_buf); |
||||
|
||||
let _ = RepoStoreInfo::create( |
||||
&self.compute_repostore_id(overlay_id, repo_id), |
||||
&key, |
||||
&self.store, |
||||
)?; // TODO in case of error, delete the previously created Overlay
|
||||
//debug_println!("KEY ADDED");
|
||||
over |
||||
} |
||||
Err(e) => return Err(e.into()), |
||||
Ok(overlay) => overlay, |
||||
}; |
||||
//debug_println!("OVERLAY FOUND");
|
||||
// add the peers to the overlay
|
||||
for advert in peers { |
||||
Peer::update_or_create(advert, &self.store)?; |
||||
overlay.add_peer(&advert.peer())?; |
||||
} |
||||
//debug_println!("PEERS ADDED");
|
||||
|
||||
// now adding the overlay_id to the account
|
||||
let account = Account::open(&user, &self.store)?; // TODO in case of error, delete the previously created Overlay
|
||||
account.add_overlay(&overlay_id)?; |
||||
//debug_println!("USER <-> OVERLAY");
|
||||
|
||||
//TODO: connect to peers
|
||||
|
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,160 @@ |
||||
/* |
||||
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
* All rights reserved. |
||||
* Licensed under the Apache License, Version 2.0 |
||||
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
* at your option. All files in the project carrying such |
||||
* notice may not be copied, modified, or distributed except |
||||
* according to those terms. |
||||
*/ |
||||
|
||||
use async_std::net::{TcpListener, TcpStream}; |
||||
use async_std::sync::Mutex; |
||||
use async_std::task; |
||||
use async_tungstenite::accept_async; |
||||
use async_tungstenite::tungstenite::protocol::Message; |
||||
use debug_print::*; |
||||
use futures::{SinkExt, StreamExt}; |
||||
use crate::broker_store_config::ConfigMode; |
||||
use crate::server::*; |
||||
use p2p_stores_lmdb::broker_store::LmdbBrokerStore; |
||||
use p2p_stores_lmdb::repo_store::LmdbRepoStore; |
||||
use std::fs; |
||||
use std::sync::Arc; |
||||
use tempfile::Builder; |
||||
use std::{thread, time}; |
||||
|
||||
pub async fn connection_loop(tcp: TcpStream, mut handler: ProtocolHandler) -> std::io::Result<()> { |
||||
let mut ws = accept_async(tcp).await.unwrap(); |
||||
let (mut tx, mut rx) = ws.split(); |
||||
|
||||
let mut tx_mutex = Arc::new(Mutex::new(tx)); |
||||
|
||||
// setup the async frames task
|
||||
let receiver = handler.async_frames_receiver(); |
||||
let ws_in_task = Arc::clone(&tx_mutex); |
||||
task::spawn(async move { |
||||
while let Ok(frame) = receiver.recv().await { |
||||
let mut sink = ws_in_task |
||||
.lock() |
||||
.await; |
||||
if sink.send(Message::binary(frame)) |
||||
.await |
||||
.is_err() |
||||
{ |
||||
break; |
||||
} |
||||
} |
||||
debug_println!("end of async frames loop"); |
||||
|
||||
let mut sink = ws_in_task.lock().await; |
||||
let _ = sink.send(Message::Close(None)).await; |
||||
let _ = sink.close().await; |
||||
}); |
||||
|
||||
while let Some(msg) = rx.next().await { |
||||
//debug_println!("RCV: {:?}", msg);
|
||||
let msg = match msg { |
||||
Err(e) => { |
||||
debug_println!("Error on server stream: {:?}", e); |
||||
// Errors returned directly through the AsyncRead/Write API are fatal, generally an error on the underlying
|
||||
// transport. closing connection
|
||||
break; |
||||
} |
||||
Ok(m) => m, |
||||
}; |
||||
//TODO implement PING messages
|
||||
if msg.is_close() { |
||||
debug_println!("CLOSE from CLIENT"); |
||||
break; |
||||
} else if msg.is_binary() { |
||||
//debug_println!("server received binary: {:?}", msg);
|
||||
|
||||
let replies = handler.handle_incoming(msg.into_data()).await; |
||||
|
||||
match replies.0 { |
||||
Err(e) => { |
||||
debug_println!("Protocol Error: {:?}", e); |
||||
// dealing with ProtocolErrors (closing the connection)
|
||||
break; |
||||
} |
||||
Ok(r) => { |
||||
if tx_mutex |
||||
.lock() |
||||
.await |
||||
.send(Message::binary(r)) |
||||
.await |
||||
.is_err() |
||||
{ |
||||
//dealing with sending errors (closing the connection)
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
match replies.1.await { |
||||
Some(errcode) => { |
||||
if errcode > 0 { |
||||
debug_println!("Close due to error code : {:?}", errcode); |
||||
//closing connection
|
||||
break; |
||||
} |
||||
} |
||||
None => {} |
||||
} |
||||
} |
||||
} |
||||
let mut sink = tx_mutex.lock().await; |
||||
let _ = sink.send(Message::Close(None)).await; |
||||
let _ = sink.close().await; |
||||
debug_println!("end of sync read+write loop"); |
||||
Ok(()) |
||||
} |
||||
|
||||
pub async fn run_server_accept_one(addrs: &str) -> std::io::Result<()> { |
||||
let root = tempfile::Builder::new() |
||||
.prefix("node-daemon") |
||||
.tempdir() |
||||
.unwrap(); |
||||
let master_key: [u8; 32] = [0; 32]; |
||||
std::fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let store = LmdbBrokerStore::open(root.path(), master_key); |
||||
|
||||
let server: BrokerServer = |
||||
BrokerServer::new(store, ConfigMode::Local).expect("starting broker"); |
||||
|
||||
let socket = TcpListener::bind(addrs).await?; |
||||
debug_println!("Listening on 127.0.0.1:3012"); |
||||
let mut connections = socket.incoming(); |
||||
let server_arc = Arc::new(server); |
||||
let tcp = connections.next().await.unwrap()?; |
||||
let proto_handler = Arc::clone(&server_arc).protocol_handler(); |
||||
let _handle = task::spawn(connection_loop(tcp, proto_handler)); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
|
||||
pub async fn run_server(addrs: &str) -> std::io::Result<()> { |
||||
let root = tempfile::Builder::new() |
||||
.prefix("node-daemon") |
||||
.tempdir() |
||||
.unwrap(); |
||||
let master_key: [u8; 32] = [0; 32]; |
||||
std::fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let store = LmdbBrokerStore::open(root.path(), master_key); |
||||
|
||||
let server: BrokerServer = |
||||
BrokerServer::new(store, ConfigMode::Local).expect("starting broker"); |
||||
|
||||
let socket = TcpListener::bind(addrs).await?; |
||||
let mut connections = socket.incoming(); |
||||
let server_arc = Arc::new(server); |
||||
while let Some(tcp) = connections.next().await { |
||||
let proto_handler = Arc::clone(&server_arc).protocol_handler(); |
||||
let _handle = task::spawn(connection_loop(tcp.unwrap(), proto_handler)); |
||||
} |
||||
Ok(()) |
||||
} |
@ -0,0 +1,24 @@ |
||||
[package] |
||||
name = "p2p-client" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "P2P Client module of NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[dependencies] |
||||
debug_print = "1.0.0" |
||||
p2p-repo = { path = "../p2p-repo" } |
||||
p2p-net = { path = "../p2p-net" } |
||||
chacha20 = "0.9.0" |
||||
serde = { version = "1.0", features = ["derive"] } |
||||
serde_bare = "0.5.0" |
||||
serde_bytes = "0.11.7" |
||||
xactor = "0.7.11" |
||||
async-trait = "0.1.57" |
||||
async-std = { version = "1.7.0", features = ["attributes"] } |
||||
futures = "0.3.24" |
||||
async-channel = "1.7.1" |
||||
async-oneshot = "0.5.0" |
||||
async-tungstenite = { version = "0.17.2", features = ["async-std-runtime","async-native-tls"] } |
@ -0,0 +1,596 @@ |
||||
/* |
||||
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
* All rights reserved. |
||||
* Licensed under the Apache License, Version 2.0 |
||||
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
* at your option. All files in the project carrying such |
||||
* notice may not be copied, modified, or distributed except |
||||
* according to those terms. |
||||
*/ |
||||
|
||||
use async_std::task; |
||||
use async_std::sync::Mutex; |
||||
use futures::{ |
||||
ready, |
||||
stream::Stream, |
||||
task::{Context, Poll}, |
||||
Future, |
||||
select, FutureExt, |
||||
}; |
||||
use futures::channel::mpsc; |
||||
use std::pin::Pin; |
||||
use std::{collections::HashSet, fmt::Debug}; |
||||
|
||||
use async_oneshot::oneshot; |
||||
use debug_print::*; |
||||
use futures::{pin_mut, stream, Sink, SinkExt, StreamExt}; |
||||
use p2p_repo::object::*; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
use p2p_net::errors::*; |
||||
use p2p_net::types::*; |
||||
use p2p_net::broker_connection::*; |
||||
use std::collections::HashMap; |
||||
use std::sync::{Arc, RwLock}; |
||||
use xactor::{message, spawn, Actor, Addr, Handler, WeakAddr}; |
||||
|
||||
|
||||
#[message] |
||||
struct BrokerMessageXActor(BrokerMessage); |
||||
|
||||
struct BrokerMessageActor { |
||||
r: Option<async_oneshot::Receiver<BrokerMessage>>, |
||||
s: async_oneshot::Sender<BrokerMessage>, |
||||
} |
||||
|
||||
impl Actor for BrokerMessageActor {} |
||||
|
||||
impl BrokerMessageActor { |
||||
fn new() -> BrokerMessageActor { |
||||
let (s, r) = oneshot::<BrokerMessage>(); |
||||
BrokerMessageActor { r: Some(r), s } |
||||
} |
||||
fn resolve(&mut self, msg: BrokerMessage) { |
||||
let _ = self.s.send(msg); |
||||
} |
||||
|
||||
fn receiver(&mut self) -> async_oneshot::Receiver<BrokerMessage> { |
||||
self.r.take().unwrap() |
||||
} |
||||
} |
||||
|
||||
struct BrokerMessageStreamActor { |
||||
r: Option<async_channel::Receiver<Block>>, |
||||
s: async_channel::Sender<Block>, |
||||
error_r: Option<async_oneshot::Receiver<Option<ProtocolError>>>, |
||||
error_s: Option<async_oneshot::Sender<Option<ProtocolError>>>, |
||||
} |
||||
|
||||
impl Actor for BrokerMessageStreamActor {} |
||||
|
||||
impl BrokerMessageStreamActor { |
||||
fn new() -> BrokerMessageStreamActor { |
||||
let (s, r) = async_channel::unbounded::<Block>(); |
||||
let (error_s, error_r) = oneshot::<Option<ProtocolError>>(); |
||||
BrokerMessageStreamActor { |
||||
r: Some(r), |
||||
s, |
||||
error_r: Some(error_r), |
||||
error_s: Some(error_s), |
||||
} |
||||
} |
||||
async fn partial(&mut self, block: Block) -> Result<(), ProtocolError> { |
||||
//debug_println!("GOT PARTIAL {:?}", block.id());
|
||||
self.s |
||||
.send(block) |
||||
.await |
||||
.map_err(|e| ProtocolError::WriteError) |
||||
} |
||||
|
||||
fn receiver(&mut self) -> async_channel::Receiver<Block> { |
||||
self.r.take().unwrap() |
||||
} |
||||
|
||||
fn error_receiver(&mut self) -> async_oneshot::Receiver<Option<ProtocolError>> { |
||||
self.error_r.take().unwrap() |
||||
} |
||||
|
||||
fn send_error(&mut self, err: Option<ProtocolError>) { |
||||
if self.error_s.is_some() { |
||||
let _ = self.error_s.take().unwrap().send(err); |
||||
self.error_s = None; |
||||
} |
||||
} |
||||
|
||||
fn close(&mut self) { |
||||
self.s.close(); |
||||
} |
||||
} |
||||
|
||||
#[async_trait::async_trait] |
||||
impl Handler<BrokerMessageXActor> for BrokerMessageActor { |
||||
async fn handle(&mut self, ctx: &mut xactor::Context<Self>, msg: BrokerMessageXActor) { |
||||
//println!("handling {:?}", msg.0);
|
||||
self.resolve(msg.0); |
||||
ctx.stop(None); |
||||
} |
||||
} |
||||
|
||||
#[async_trait::async_trait] |
||||
impl Handler<BrokerMessageXActor> for BrokerMessageStreamActor { |
||||
async fn handle(&mut self, ctx: &mut xactor::Context<Self>, msg: BrokerMessageXActor) { |
||||
//println!("handling {:?}", msg.0);
|
||||
let res: Result<Option<Block>, ProtocolError> = msg.0.into(); |
||||
match res { |
||||
Err(e) => { |
||||
self.send_error(Some(e)); |
||||
ctx.stop(None); |
||||
self.close(); |
||||
} |
||||
Ok(Some(b)) => { |
||||
self.send_error(None); |
||||
// it must be a partial content
|
||||
let res = self.partial(b).await; |
||||
if let Err(e) = res { |
||||
ctx.stop(None); |
||||
self.close(); |
||||
} |
||||
} |
||||
Ok(None) => { |
||||
self.send_error(None); |
||||
ctx.stop(None); |
||||
self.close(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub struct ConnectionRemote {} |
||||
|
||||
impl ConnectionRemote { |
||||
pub async fn ext_request< |
||||
B: Stream<Item = Vec<u8>> + StreamExt + Send + Sync, |
||||
A: Sink<Vec<u8>, Error = ProtocolError> + Send, |
||||
>( |
||||
w: A, |
||||
r: B, |
||||
request: ExtRequest, |
||||
) -> Result<ExtResponse, ProtocolError> { |
||||
unimplemented!(); |
||||
} |
||||
|
||||
async fn close<S>(w: S, err: ProtocolError) -> ProtocolError |
||||
where |
||||
S: Sink<Vec<u8>, Error = ProtocolError>, |
||||
{ |
||||
let mut writer = Box::pin(w); |
||||
let _ = writer.send(vec![]); |
||||
let _ = writer.close().await; |
||||
err |
||||
} |
||||
|
||||
pub async fn open_broker_connection< |
||||
B: Stream<Item = Vec<u8>> + StreamExt + Send + Sync + 'static, |
||||
A: Sink<Vec<u8>, Error = ProtocolError> + Send + 'static, |
||||
>( |
||||
w: A, |
||||
r: B, |
||||
user: PubKey, |
||||
user_pk: PrivKey, |
||||
client: PubKey, |
||||
) -> Result<impl BrokerConnection, ProtocolError> { |
||||
let mut writer = Box::pin(w); |
||||
writer |
||||
.send(serde_bare::to_vec(&StartProtocol::Auth(ClientHello::V0()))?) |
||||
.await |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
|
||||
let mut reader = Box::pin(r); |
||||
let answer = reader.next().await; |
||||
if answer.is_none() { |
||||
return Err(Self::close(writer, ProtocolError::InvalidState).await); |
||||
} |
||||
|
||||
let server_hello = serde_bare::from_slice::<ServerHello>(&answer.unwrap())?; |
||||
|
||||
//debug_println!("received nonce from server: {:?}", server_hello.nonce());
|
||||
|
||||
let content = ClientAuthContentV0 { |
||||
user, |
||||
client, |
||||
nonce: server_hello.nonce().clone(), |
||||
}; |
||||
|
||||
let sig = sign(user_pk, user, &serde_bare::to_vec(&content)?) |
||||
.map_err(|_e| ProtocolError::SignatureError)?; |
||||
|
||||
let auth_ser = serde_bare::to_vec(&ClientAuth::V0(ClientAuthV0 { content, sig }))?; |
||||
//debug_println!("AUTH SENT {:?}", auth_ser);
|
||||
writer |
||||
.send(auth_ser) |
||||
.await |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
|
||||
let answer = reader.next().await; |
||||
if answer.is_none() { |
||||
//return Err(ProtocolError::InvalidState);
|
||||
return Err(Self::close(writer, ProtocolError::InvalidState).await); |
||||
} |
||||
|
||||
let auth_result = serde_bare::from_slice::<AuthResult>(&answer.unwrap())?; |
||||
|
||||
match auth_result.result() { |
||||
0 => { |
||||
async fn transform(message: BrokerMessage) -> Result<Vec<u8>, ProtocolError> { |
||||
if message.is_close() { |
||||
Ok(vec![]) |
||||
} else { |
||||
Ok(serde_bare::to_vec(&message)?) |
||||
} |
||||
} |
||||
let messages_stream_write = writer.with(|message| transform(message)); |
||||
|
||||
let mut messages_stream_read = reader.map(|message| { |
||||
if message.len() == 0 { |
||||
BrokerMessage::Close |
||||
} else { |
||||
match serde_bare::from_slice::<BrokerMessage>(&message) { |
||||
Err(e) => BrokerMessage::Close, |
||||
Ok(m) => m |
||||
} |
||||
} |
||||
}); |
||||
|
||||
let cnx = |
||||
BrokerConnectionRemote::open(messages_stream_write, messages_stream_read, user); |
||||
|
||||
Ok(cnx) |
||||
} |
||||
err => Err(Self::close(writer, ProtocolError::try_from(err).unwrap()).await), |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub struct BrokerConnectionRemote<T> |
||||
where |
||||
T: Sink<BrokerMessage> + Send + 'static, |
||||
{ |
||||
writer: Arc<Mutex<Pin<Box<T>>>>, |
||||
user: PubKey, |
||||
actors: Arc<RwLock<HashMap<u64, WeakAddr<BrokerMessageActor>>>>, |
||||
stream_actors: Arc<RwLock<HashMap<u64, WeakAddr<BrokerMessageStreamActor>>>>, |
||||
shutdown: mpsc::UnboundedSender<Void>, |
||||
} |
||||
|
||||
#[async_trait::async_trait] |
||||
impl<T> BrokerConnection for BrokerConnectionRemote<T> |
||||
where |
||||
T: Sink<BrokerMessage> + Send, |
||||
{ |
||||
type OC = BrokerConnectionRemote<T>; |
||||
type BlockStream = async_channel::Receiver<Block>; |
||||
|
||||
async fn close(&mut self) { |
||||
let _ = self.shutdown.close().await; |
||||
let mut w = self.writer.lock().await; |
||||
let _ = w.send(BrokerMessage::Close).await; |
||||
let _ = w.close().await; |
||||
} |
||||
|
||||
async fn process_overlay_request_stream_response( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<Pin<Box<Self::BlockStream>>, ProtocolError> { |
||||
let mut actor = BrokerMessageStreamActor::new(); |
||||
let receiver = actor.receiver(); |
||||
let error_receiver = actor.error_receiver(); |
||||
let mut addr = actor |
||||
.start() |
||||
.await |
||||
.map_err(|_e| ProtocolError::ActorError)?; |
||||
|
||||
let request_id = addr.actor_id(); |
||||
//debug_println!("actor ID {}", request_id);
|
||||
|
||||
{ |
||||
let mut map = self.stream_actors.write().expect("RwLock poisoned"); |
||||
map.insert(request_id, addr.downgrade()); |
||||
} |
||||
|
||||
let mut w = self.writer.lock().await; |
||||
w.send(BrokerMessage::V0(BrokerMessageV0 { |
||||
padding: vec![], //FIXME implement padding
|
||||
content: BrokerMessageContentV0::BrokerOverlayMessage(BrokerOverlayMessage::V0( |
||||
BrokerOverlayMessageV0 { |
||||
overlay, |
||||
content: BrokerOverlayMessageContentV0::BrokerOverlayRequest( |
||||
BrokerOverlayRequest::V0(BrokerOverlayRequestV0 { |
||||
id: request_id, |
||||
content: request, |
||||
}), |
||||
), |
||||
}, |
||||
)), |
||||
})) |
||||
.await |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
|
||||
//debug_println!("waiting for first reply");
|
||||
let reply = error_receiver.await; |
||||
match reply { |
||||
Err(_e) => { |
||||
Err(ProtocolError::Closing) |
||||
} |
||||
Ok(Some(e)) => { |
||||
let mut map = self.stream_actors.write().expect("RwLock poisoned"); |
||||
map.remove(&request_id); |
||||
return Err(e); |
||||
} |
||||
Ok(None) => { |
||||
let stream_actors_in_thread = Arc::clone(&self.stream_actors); |
||||
task::spawn(async move { |
||||
addr.wait_for_stop().await; // TODO add timeout
|
||||
let mut map = stream_actors_in_thread.write().expect("RwLock poisoned"); |
||||
map.remove(&request_id); |
||||
}); |
||||
|
||||
Ok(Box::pin(receiver)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
async fn process_overlay_request_objectid_response( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<ObjectId, ProtocolError> { |
||||
before!(self, request_id, addr, receiver); |
||||
|
||||
self.writer.lock().await |
||||
.send(BrokerMessage::V0(BrokerMessageV0 { |
||||
padding: vec![], // FIXME implement padding
|
||||
content: BrokerMessageContentV0::BrokerOverlayMessage(BrokerOverlayMessage::V0( |
||||
BrokerOverlayMessageV0 { |
||||
overlay, |
||||
content: BrokerOverlayMessageContentV0::BrokerOverlayRequest( |
||||
BrokerOverlayRequest::V0(BrokerOverlayRequestV0 { |
||||
id: request_id, |
||||
content: request, |
||||
}), |
||||
), |
||||
}, |
||||
)), |
||||
})) |
||||
.await |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
|
||||
after!(self, request_id, addr, receiver, reply); |
||||
reply.into() |
||||
} |
||||
|
||||
async fn process_overlay_request( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<(), ProtocolError> { |
||||
before!(self, request_id, addr, receiver); |
||||
|
||||
self.writer.lock().await |
||||
.send(BrokerMessage::V0(BrokerMessageV0 { |
||||
padding: vec![], // FIXME implement padding
|
||||
content: BrokerMessageContentV0::BrokerOverlayMessage(BrokerOverlayMessage::V0( |
||||
BrokerOverlayMessageV0 { |
||||
overlay, |
||||
content: BrokerOverlayMessageContentV0::BrokerOverlayRequest( |
||||
BrokerOverlayRequest::V0(BrokerOverlayRequestV0 { |
||||
id: request_id, |
||||
content: request, |
||||
}), |
||||
), |
||||
}, |
||||
)), |
||||
})) |
||||
.await |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
|
||||
after!(self, request_id, addr, receiver, reply); |
||||
reply.into() |
||||
} |
||||
|
||||
async fn add_user( |
||||
&mut self, |
||||
user_id: PubKey, |
||||
admin_user_pk: PrivKey, |
||||
) -> Result<(), ProtocolError> { |
||||
before!(self, request_id, addr, receiver); |
||||
|
||||
let op_content = AddUserContentV0 { user: user_id }; |
||||
|
||||
let sig = sign( |
||||
admin_user_pk, |
||||
self.user, |
||||
&serde_bare::to_vec(&op_content)?, |
||||
)?; |
||||
|
||||
self.writer.lock().await |
||||
.send(BrokerMessage::V0(BrokerMessageV0 { |
||||
padding: vec![], // TODO implement padding
|
||||
content: BrokerMessageContentV0::BrokerRequest(BrokerRequest::V0( |
||||
BrokerRequestV0 { |
||||
id: request_id, |
||||
content: BrokerRequestContentV0::AddUser(AddUser::V0(AddUserV0 { |
||||
content: op_content, |
||||
sig, |
||||
})), |
||||
}, |
||||
)), |
||||
})) |
||||
.await |
||||
.map_err(|_e| ProtocolError::WriteError)?; |
||||
|
||||
after!(self, request_id, addr, receiver, reply); |
||||
reply.into() |
||||
} |
||||
|
||||
async fn del_user(&mut self, user_id: PubKey, admin_user_pk: PrivKey) {} |
||||
|
||||
async fn add_client(&mut self, client_id: ClientId, user_pk: PrivKey) {} |
||||
|
||||
async fn del_client(&mut self, client_id: ClientId, user_pk: PrivKey) {} |
||||
|
||||
async fn overlay_connect( |
||||
&mut self, |
||||
repo_link: &RepoLink, |
||||
public: bool, |
||||
) -> Result<OverlayConnectionClient<BrokerConnectionRemote<T>>, ProtocolError> { |
||||
let overlay = self.process_overlay_connect(repo_link, public).await?; |
||||
|
||||
Ok(OverlayConnectionClient::create(self, overlay,repo_link.clone() )) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
enum Void {} |
||||
|
||||
impl<T> BrokerConnectionRemote<T> |
||||
where |
||||
T: Sink<BrokerMessage> + Send, |
||||
{ |
||||
async fn connection_reader_loop< |
||||
U: Stream<Item = BrokerMessage> + StreamExt + Send + Sync + Unpin + 'static, |
||||
>( |
||||
stream: U, |
||||
actors: Arc<RwLock<HashMap<u64, WeakAddr<BrokerMessageActor>>>>, |
||||
stream_actors: Arc<RwLock<HashMap<u64, WeakAddr<BrokerMessageStreamActor>>>>, |
||||
shutdown: mpsc::UnboundedReceiver<Void>, |
||||
) -> Result<(), ProtocolError> { |
||||
let mut s = stream.fuse(); |
||||
let mut shutdown = shutdown.fuse(); |
||||
loop { |
||||
select! { |
||||
void = shutdown.next().fuse() => match void { |
||||
Some(void) => match void {}, |
||||
None => break, |
||||
}, |
||||
message = s.next().fuse() => match message { |
||||
Some(message) =>
|
||||
{ |
||||
//debug_println!("GOT MESSAGE {:?}", message);
|
||||
|
||||
if message.is_close() { |
||||
// releasing the blocking calls on the actors
|
||||
|
||||
let map = actors.read().expect("RwLock poisoned"); |
||||
for (a) in map.values() { |
||||
if let Some(mut addr) = a.upgrade() { |
||||
let _ = addr.stop(Some(ProtocolError::Closing.into())); |
||||
} |
||||
} |
||||
let map2 = stream_actors.read().expect("RwLock poisoned"); |
||||
for (a) in map2.values() { |
||||
if let Some(mut addr) = a.upgrade() { |
||||
let _ = addr.stop(Some(ProtocolError::Closing.into())); |
||||
} |
||||
} |
||||
return Err(ProtocolError::Closing); |
||||
} |
||||
|
||||
if message.is_request() { |
||||
debug_println!("is request {}", message.id()); |
||||
// closing connection. a client is not supposed to receive requests.
|
||||
return Err(ProtocolError::Closing); |
||||
|
||||
} else if message.is_response() { |
||||
let id = message.id(); |
||||
//debug_println!("is response for {}", id);
|
||||
{ |
||||
let map = actors.read().expect("RwLock poisoned"); |
||||
match map.get(&id) { |
||||
Some(weak_addr) => match weak_addr.upgrade() { |
||||
Some(addr) => { |
||||
addr.send(BrokerMessageXActor(message)) |
||||
.map_err(|e| ProtocolError::Closing)? |
||||
//.expect("sending message back to actor failed");
|
||||
} |
||||
None => { |
||||
debug_println!("ERROR. Addr is dead for ID {}", id); |
||||
return Err(ProtocolError::Closing); |
||||
} |
||||
}, |
||||
None => { |
||||
let map2 = stream_actors.read().expect("RwLock poisoned"); |
||||
match map2.get(&id) { |
||||
Some(weak_addr) => match weak_addr.upgrade() { |
||||
Some(addr) => { |
||||
addr.send(BrokerMessageXActor(message)) |
||||
.map_err(|e| ProtocolError::Closing)? |
||||
//.expect("sending message back to stream actor failed");
|
||||
} |
||||
None => { |
||||
debug_println!( |
||||
"ERROR. Addr is dead for ID {} {:?}", |
||||
id, |
||||
message |
||||
); |
||||
return Err(ProtocolError::Closing); |
||||
} |
||||
}, |
||||
None => { |
||||
debug_println!("Actor ID not found {} {:?}", id, message); |
||||
return Err(ProtocolError::Closing); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
None => break, |
||||
} |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn open<U: Stream<Item = BrokerMessage> + StreamExt + Send + Sync + Unpin + 'static>( |
||||
writer: T, |
||||
reader: U, |
||||
user: PubKey, |
||||
) -> BrokerConnectionRemote<T> { |
||||
let actors: Arc<RwLock<HashMap<u64, WeakAddr<BrokerMessageActor>>>> = |
||||
Arc::new(RwLock::new(HashMap::new())); |
||||
|
||||
let stream_actors: Arc<RwLock<HashMap<u64, WeakAddr<BrokerMessageStreamActor>>>> = |
||||
Arc::new(RwLock::new(HashMap::new())); |
||||
|
||||
let (shutdown_sender, shutdown_receiver) = mpsc::unbounded::<Void>(); |
||||
|
||||
let w = Arc::new(Mutex::new(Box::pin(writer))); |
||||
let ws_in_task = Arc::clone(&w); |
||||
|
||||
let actors_in_thread = Arc::clone(&actors); |
||||
let stream_actors_in_thread = Arc::clone(&stream_actors); |
||||
task::spawn(async move { |
||||
debug_println!("START of reader loop"); |
||||
if let Err(e) = |
||||
Self::connection_reader_loop(reader, actors_in_thread, stream_actors_in_thread, shutdown_receiver) |
||||
.await |
||||
{ |
||||
debug_println!("closing because of {}", e); |
||||
let _ = ws_in_task.lock().await.close().await; |
||||
} |
||||
debug_println!("END of reader loop"); |
||||
}); |
||||
|
||||
BrokerConnectionRemote::<T> { |
||||
writer: Arc::clone(&w), |
||||
user, |
||||
actors: Arc::clone(&actors), |
||||
stream_actors: Arc::clone(&stream_actors), |
||||
shutdown:shutdown_sender , |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,95 @@ |
||||
/* |
||||
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
* All rights reserved. |
||||
* Licensed under the Apache License, Version 2.0 |
||||
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
* at your option. All files in the project carrying such |
||||
* notice may not be copied, modified, or distributed except |
||||
* according to those terms. |
||||
*/ |
||||
|
||||
use debug_print::*; |
||||
|
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::{generate_keypair, now_timestamp}; |
||||
use p2p_net::errors::*; |
||||
use p2p_net::types::*; |
||||
use p2p_net::broker_connection::*; |
||||
use crate::connection_remote::*; |
||||
use futures::{future, pin_mut, stream, SinkExt, StreamExt}; |
||||
|
||||
use async_tungstenite::async_std::connect_async; |
||||
use async_tungstenite::client_async; |
||||
use async_tungstenite::tungstenite::{Error, Message}; |
||||
|
||||
pub struct BrokerConnectionWebSocket { |
||||
|
||||
} |
||||
|
||||
impl BrokerConnectionWebSocket{ |
||||
|
||||
pub async fn open(url:&str, priv_key: PrivKey, pub_key: PubKey) -> Result<impl BrokerConnection, ProtocolError>
|
||||
{ |
||||
|
||||
let res = connect_async(url).await; |
||||
|
||||
match (res) { |
||||
Ok((ws, _)) => { |
||||
debug_println!("WebSocket handshake completed"); |
||||
|
||||
let (write, read) = ws.split(); |
||||
let mut frames_stream_read = read.map(|msg_res| match msg_res { |
||||
Err(e) => { |
||||
debug_println!("ERROR {:?}", e); |
||||
vec![] |
||||
} |
||||
Ok(message) => { |
||||
if message.is_close() { |
||||
debug_println!("CLOSE FROM SERVER"); |
||||
vec![] |
||||
} else { |
||||
message.into_data() |
||||
} |
||||
} |
||||
}); |
||||
async fn transform(message: Vec<u8>) -> Result<Message, Error> { |
||||
if message.len() == 0 { |
||||
debug_println!("sending CLOSE message to SERVER"); |
||||
Ok(Message::Close(None)) |
||||
} else { |
||||
Ok(Message::binary(message)) |
||||
} |
||||
} |
||||
let frames_stream_write = write |
||||
.with(|message| transform(message)) |
||||
.sink_map_err(|e| ProtocolError::WriteError); |
||||
|
||||
let master_key: [u8; 32] = [0; 32]; |
||||
let mut cnx_res = ConnectionRemote::open_broker_connection( |
||||
frames_stream_write, |
||||
frames_stream_read, |
||||
pub_key, |
||||
priv_key, |
||||
PubKey::Ed25519PubKey([1; 32]), |
||||
) |
||||
.await; |
||||
|
||||
match cnx_res { |
||||
Ok(mut cnx) => { |
||||
Ok(cnx) |
||||
} |
||||
Err(e) => { |
||||
debug_println!("cannot connect {:?}", e); |
||||
Err(e) |
||||
} |
||||
} |
||||
} |
||||
Err(e) => { |
||||
debug_println!("Cannot connect: {:?}", e); |
||||
Err(ProtocolError::ConnectionError) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,47 @@ |
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
#[macro_export] |
||||
macro_rules! before { |
||||
( $self:expr, $request_id:ident, $addr:ident, $receiver:ident ) => { |
||||
let mut actor = BrokerMessageActor::new(); |
||||
let $receiver = actor.receiver(); |
||||
let mut $addr = actor |
||||
.start() |
||||
.await |
||||
.map_err(|_e| ProtocolError::ActorError)?; |
||||
|
||||
let $request_id = $addr.actor_id(); |
||||
//debug_println!("actor ID {}", $request_id);
|
||||
|
||||
{ |
||||
let mut map = $self.actors.write().expect("RwLock poisoned"); |
||||
map.insert($request_id, $addr.downgrade()); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
macro_rules! after { |
||||
( $self:expr, $request_id:ident, $addr:ident, $receiver:ident, $reply:ident ) => { |
||||
//debug_println!("waiting for reply");
|
||||
|
||||
$addr.wait_for_stop().await; // TODO add timeout and close connection if there's no reply
|
||||
let r = $receiver.await; |
||||
if r.is_err() { return Err(ProtocolError::Closing);} |
||||
let $reply = r.unwrap(); |
||||
//debug_println!("reply arrived {:?}", $reply);
|
||||
{ |
||||
let mut map = $self.actors.write().expect("RwLock poisoned"); |
||||
map.remove(&$request_id); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
pub mod connection_remote; |
||||
|
||||
pub mod connection_ws; |
@ -0,0 +1,20 @@ |
||||
[package] |
||||
name = "p2p-net" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "P2P network module of NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[dependencies] |
||||
debug_print = "1.0.0" |
||||
p2p-repo = { path = "../p2p-repo" } |
||||
serde = { version = "1.0", features = ["derive"] } |
||||
serde_bare = "0.5.0" |
||||
serde_bytes = "0.11.7" |
||||
num_enum = "0.5.7" |
||||
async-broadcast = "0.4.1" |
||||
futures = "0.3.24" |
||||
async-trait = "0.1.57" |
||||
blake3 = "1.3.1" |
@ -0,0 +1,337 @@ |
||||
/* |
||||
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
* All rights reserved. |
||||
* Licensed under the Apache License, Version 2.0 |
||||
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
* at your option. All files in the project carrying such |
||||
* notice may not be copied, modified, or distributed except |
||||
* according to those terms. |
||||
*/ |
||||
|
||||
//! Connection to a Broker, can be local or remote.
|
||||
//! If remote, it will use a Stream and Sink of framed messages
|
||||
//! This is the trait
|
||||
//!
|
||||
|
||||
use futures::{ |
||||
ready, |
||||
stream::Stream, |
||||
task::{Context, Poll}, |
||||
Future, |
||||
select, FutureExt, |
||||
}; |
||||
use futures::channel::mpsc; |
||||
use std::pin::Pin; |
||||
use std::{collections::HashSet, fmt::Debug}; |
||||
|
||||
use async_broadcast::{broadcast, Receiver}; |
||||
use debug_print::*; |
||||
use futures::{pin_mut, stream, Sink, SinkExt, StreamExt}; |
||||
use p2p_repo::object::*; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
use crate::errors::*; |
||||
use crate::types::*; |
||||
|
||||
|
||||
#[async_trait::async_trait] |
||||
pub trait BrokerConnection { |
||||
type OC: BrokerConnection; |
||||
type BlockStream: Stream<Item = Block>; |
||||
|
||||
async fn close(&mut self); |
||||
|
||||
async fn add_user( |
||||
&mut self, |
||||
user_id: PubKey, |
||||
admin_user_pk: PrivKey, |
||||
) -> Result<(), ProtocolError>; |
||||
|
||||
async fn del_user(&mut self, user_id: PubKey, admin_user_pk: PrivKey); |
||||
|
||||
async fn add_client(&mut self, client_id: ClientId, user_pk: PrivKey); |
||||
|
||||
async fn del_client(&mut self, client_id: ClientId, user_pk: PrivKey); |
||||
|
||||
async fn overlay_connect( |
||||
&mut self, |
||||
repo: &RepoLink, |
||||
public: bool, |
||||
) -> Result<OverlayConnectionClient<Self::OC>, ProtocolError>; |
||||
|
||||
// TODO: remove those 4 functions from trait. they are used internally only. should not be exposed to end-user
|
||||
async fn process_overlay_request( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<(), ProtocolError>; |
||||
|
||||
async fn process_overlay_request_stream_response( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<Pin<Box<Self::BlockStream>>, ProtocolError>; |
||||
|
||||
async fn process_overlay_request_objectid_response( |
||||
&mut self, |
||||
overlay: OverlayId, |
||||
request: BrokerOverlayRequestContentV0, |
||||
) -> Result<ObjectId, ProtocolError>; |
||||
|
||||
async fn process_overlay_connect( |
||||
&mut self, |
||||
repo_link: &RepoLink, |
||||
public: bool, |
||||
) -> Result<OverlayId, ProtocolError> { |
||||
let overlay: OverlayId = match public { |
||||
true => Digest::Blake3Digest32(*blake3::hash(repo_link.id().slice()).as_bytes()), |
||||
false => { |
||||
let key: [u8; blake3::OUT_LEN] = |
||||
blake3::derive_key("NextGraph OverlayId BLAKE3 key", repo_link.secret().slice()); |
||||
let keyed_hash = blake3::keyed_hash(&key, repo_link.id().slice()); |
||||
Digest::Blake3Digest32(*keyed_hash.as_bytes()) |
||||
} |
||||
}; |
||||
|
||||
let res = self |
||||
.process_overlay_request( |
||||
overlay, |
||||
BrokerOverlayRequestContentV0::OverlayConnect(OverlayConnect::V0()), |
||||
) |
||||
.await; |
||||
|
||||
match res { |
||||
Err(e) => { |
||||
if e == ProtocolError::OverlayNotJoined { |
||||
debug_println!("OverlayNotJoined"); |
||||
let res2 = self |
||||
.process_overlay_request( |
||||
overlay, |
||||
BrokerOverlayRequestContentV0::OverlayJoin(OverlayJoin::V0( |
||||
OverlayJoinV0 { |
||||
secret: repo_link.secret(), |
||||
peers: repo_link.peers(), |
||||
repo_pubkey: Some(repo_link.id()), //TODO if we know we are connecting to a core node, we can pass None here
|
||||
}, |
||||
)), |
||||
) |
||||
.await?; |
||||
} else { |
||||
return Err(e); |
||||
} |
||||
} |
||||
Ok(()) => {} |
||||
} |
||||
|
||||
debug_println!("OverlayConnectionClient ready"); |
||||
Ok(overlay) |
||||
} |
||||
} |
||||
|
||||
pub struct OverlayConnectionClient<'a, T> |
||||
where |
||||
T: BrokerConnection, |
||||
{ |
||||
broker: &'a mut T, |
||||
overlay: OverlayId, |
||||
repo_link: RepoLink, |
||||
} |
||||
|
||||
impl<'a, T> OverlayConnectionClient<'a, T> |
||||
where |
||||
T: BrokerConnection, |
||||
{ |
||||
pub fn create( broker: &'a mut T, overlay: OverlayId, repo_link: RepoLink) -> OverlayConnectionClient<'a, T> { |
||||
OverlayConnectionClient { |
||||
broker, |
||||
repo_link, |
||||
overlay, |
||||
} |
||||
} |
||||
|
||||
pub fn overlay(repo_link: &RepoLink, public: bool) -> OverlayId { |
||||
let overlay: OverlayId = match public { |
||||
true => Digest::Blake3Digest32(*blake3::hash(repo_link.id().slice()).as_bytes()), |
||||
false => { |
||||
let key: [u8; blake3::OUT_LEN] = |
||||
blake3::derive_key("NextGraph OverlayId BLAKE3 key", repo_link.secret().slice()); |
||||
let keyed_hash = blake3::keyed_hash(&key, repo_link.id().slice()); |
||||
Digest::Blake3Digest32(*keyed_hash.as_bytes()) |
||||
} |
||||
}; |
||||
overlay |
||||
} |
||||
|
||||
pub async fn sync_branch( |
||||
&mut self, |
||||
heads: Vec<ObjectId>, |
||||
known_heads: Vec<ObjectId>, |
||||
known_commits: BloomFilter, |
||||
) -> Result<Pin<Box<T::BlockStream>>, ProtocolError> { |
||||
self.broker |
||||
.process_overlay_request_stream_response( |
||||
self.overlay, |
||||
BrokerOverlayRequestContentV0::BranchSyncReq(BranchSyncReq::V0(BranchSyncReqV0 { |
||||
heads, |
||||
known_heads, |
||||
known_commits, |
||||
})), |
||||
) |
||||
.await |
||||
} |
||||
|
||||
pub fn leave(&self) {} |
||||
|
||||
pub fn topic_connect(&self, id: TopicId) -> TopicSubscription<T> { |
||||
let (s, mut r1) = broadcast(128); // FIXME this should be done only once, in the Broker
|
||||
TopicSubscription { |
||||
id, |
||||
overlay_cnx: self, |
||||
event_stream: r1.clone(), |
||||
} |
||||
} |
||||
|
||||
pub async fn delete_object(&mut self, id: ObjectId) -> Result<(), ProtocolError> { |
||||
self.broker |
||||
.process_overlay_request( |
||||
self.overlay, |
||||
BrokerOverlayRequestContentV0::ObjectDel(ObjectDel::V0(ObjectDelV0 { id })), |
||||
) |
||||
.await |
||||
} |
||||
|
||||
pub async fn pin_object(&mut self, id: ObjectId) -> Result<(), ProtocolError> { |
||||
self.broker |
||||
.process_overlay_request( |
||||
self.overlay, |
||||
BrokerOverlayRequestContentV0::ObjectPin(ObjectPin::V0(ObjectPinV0 { id })), |
||||
) |
||||
.await |
||||
} |
||||
|
||||
pub async fn unpin_object(&mut self, id: ObjectId) -> Result<(), ProtocolError> { |
||||
self.broker |
||||
.process_overlay_request( |
||||
self.overlay, |
||||
BrokerOverlayRequestContentV0::ObjectUnpin(ObjectUnpin::V0(ObjectUnpinV0 { id })), |
||||
) |
||||
.await |
||||
} |
||||
|
||||
pub async fn copy_object( |
||||
&mut self, |
||||
id: ObjectId, |
||||
expiry: Option<Timestamp>, |
||||
) -> Result<ObjectId, ProtocolError> { |
||||
self.broker |
||||
.process_overlay_request_objectid_response( |
||||
self.overlay, |
||||
BrokerOverlayRequestContentV0::ObjectCopy(ObjectCopy::V0(ObjectCopyV0 { |
||||
id, |
||||
expiry, |
||||
})), |
||||
) |
||||
.await |
||||
} |
||||
|
||||
pub async fn get_block( |
||||
&mut self, |
||||
id: BlockId, |
||||
include_children: bool, |
||||
topic: Option<PubKey>, |
||||
) -> Result<Pin<Box<T::BlockStream>>, ProtocolError> { |
||||
self.broker |
||||
.process_overlay_request_stream_response( |
||||
self.overlay, |
||||
BrokerOverlayRequestContentV0::BlockGet(BlockGet::V0(BlockGetV0 { |
||||
id, |
||||
include_children, |
||||
topic, |
||||
})), |
||||
) |
||||
.await |
||||
} |
||||
|
||||
pub async fn get_object( |
||||
&mut self, |
||||
id: ObjectId, |
||||
topic: Option<PubKey>, |
||||
) -> Result<Object, ProtocolError> { |
||||
let mut blockstream = self.get_block(id, true, topic).await?; |
||||
let mut store = HashMapRepoStore::new(); |
||||
while let Some(block) = blockstream.next().await { |
||||
store.put(&block).unwrap(); |
||||
} |
||||
Object::load(id, None, &store).map_err(|e| match e { |
||||
ObjectParseError::MissingBlocks(_missing) => ProtocolError::MissingBlocks, |
||||
_ => ProtocolError::ObjectParseError, |
||||
}) |
||||
} |
||||
|
||||
pub async fn put_block(&mut self, block: &Block) -> Result<BlockId, ProtocolError> { |
||||
self.broker |
||||
.process_overlay_request( |
||||
self.overlay, |
||||
BrokerOverlayRequestContentV0::BlockPut(BlockPut::V0(block.clone())), |
||||
) |
||||
.await?; |
||||
Ok(block.id()) |
||||
} |
||||
|
||||
// TODO maybe implement a put_block_with_children ? that would behave like put_object, but taking in a parent Blockk instead of a content
|
||||
|
||||
pub async fn put_object( |
||||
&mut self, |
||||
content: ObjectContent, |
||||
deps: Vec<ObjectId>, |
||||
expiry: Option<Timestamp>, |
||||
max_object_size: usize, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
) -> Result<ObjectId, ProtocolError> { |
||||
let obj = Object::new( |
||||
content, |
||||
deps, |
||||
expiry, |
||||
max_object_size, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
); |
||||
debug_println!("object has {} blocks", obj.blocks().len()); |
||||
let mut deduplicated: HashSet<ObjectId> = HashSet::new(); |
||||
for block in obj.blocks() { |
||||
let id = block.id(); |
||||
if deduplicated.get(&id).is_none() { |
||||
let _ = self.put_block(block).await?; |
||||
deduplicated.insert(id); |
||||
} |
||||
} |
||||
Ok(obj.id()) |
||||
} |
||||
} |
||||
|
||||
pub struct TopicSubscription<'a, T> |
||||
where |
||||
T: BrokerConnection, |
||||
{ |
||||
id: TopicId, |
||||
overlay_cnx: &'a OverlayConnectionClient<'a, T>, |
||||
event_stream: Receiver<Event>, |
||||
} |
||||
|
||||
impl<'a, T> TopicSubscription<'a, T> |
||||
where |
||||
T: BrokerConnection, |
||||
{ |
||||
pub fn unsubscribe(&self) {} |
||||
|
||||
pub fn disconnect(&self) {} |
||||
|
||||
pub fn get_branch_heads(&self) {} |
||||
|
||||
pub fn get_event_stream(&self) -> &Receiver<Event> { |
||||
&self.event_stream |
||||
} |
||||
} |
@ -0,0 +1,162 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use crate::types::BrokerMessage; |
||||
use core::fmt; |
||||
use p2p_repo::object::ObjectParseError; |
||||
use p2p_repo::types::Block; |
||||
use p2p_repo::types::ObjectId; |
||||
use num_enum::IntoPrimitive; |
||||
use num_enum::TryFromPrimitive; |
||||
use std::convert::From; |
||||
use std::convert::TryFrom; |
||||
use std::error::Error; |
||||
|
||||
#[derive(Debug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive, Clone)] |
||||
#[repr(u16)] |
||||
pub enum ProtocolError { |
||||
WriteError = 1, |
||||
ActorError, |
||||
InvalidState, |
||||
SignatureError, |
||||
InvalidSignature, |
||||
SerializationError, |
||||
PartialContent, |
||||
AccessDenied, |
||||
OverlayNotJoined, |
||||
OverlayNotFound, |
||||
BrokerError, |
||||
NotFound, |
||||
EndOfStream, |
||||
StoreError, |
||||
MissingBlocks, |
||||
ObjectParseError, |
||||
InvalidValue, |
||||
UserAlreadyExists, |
||||
RepoIdRequired, |
||||
Closing, |
||||
ConnectionError, |
||||
} |
||||
|
||||
impl ProtocolError { |
||||
pub fn is_stream(&self) -> bool { |
||||
*self == ProtocolError::PartialContent || *self == ProtocolError::EndOfStream |
||||
} |
||||
} |
||||
|
||||
impl Error for ProtocolError {} |
||||
|
||||
impl fmt::Display for ProtocolError { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{:?}", self) |
||||
} |
||||
} |
||||
|
||||
impl From<p2p_repo::errors::NgError> for ProtocolError { |
||||
fn from(e: p2p_repo::errors::NgError) -> Self { |
||||
match e { |
||||
p2p_repo::errors::NgError::InvalidSignature => ProtocolError::InvalidSignature, |
||||
p2p_repo::errors::NgError::SerializationError => ProtocolError::SerializationError, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<ObjectParseError> for ProtocolError { |
||||
fn from(e: ObjectParseError) -> Self { |
||||
ProtocolError::ObjectParseError |
||||
} |
||||
} |
||||
|
||||
impl From<p2p_repo::store::StorageError> for ProtocolError { |
||||
fn from(e: p2p_repo::store::StorageError) -> Self { |
||||
match e { |
||||
p2p_repo::store::StorageError::NotFound => ProtocolError::NotFound, |
||||
p2p_repo::store::StorageError::InvalidValue => ProtocolError::InvalidValue, |
||||
_ => ProtocolError::StoreError, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<serde_bare::error::Error> for ProtocolError { |
||||
fn from(e: serde_bare::error::Error) -> Self { |
||||
ProtocolError::SerializationError |
||||
} |
||||
} |
||||
|
||||
impl From<BrokerMessage> for Result<(), ProtocolError> { |
||||
fn from(msg: BrokerMessage) -> Self { |
||||
if !msg.is_response() { |
||||
panic!("BrokerMessage is not a response"); |
||||
} |
||||
match msg.result() { |
||||
0 => Ok(()), |
||||
err => Err(ProtocolError::try_from(err).unwrap()), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<BrokerMessage> for Result<ObjectId, ProtocolError> { |
||||
fn from(msg: BrokerMessage) -> Self { |
||||
if !msg.is_response() { |
||||
panic!("BrokerMessage is not a response"); |
||||
} |
||||
match msg.result() { |
||||
0 => Ok(msg.response_object_id()), |
||||
err => Err(ProtocolError::try_from(err).unwrap()), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Option represents if a Block is available. cannot be returned here. call BrokerMessage.response_block() to get a reference to it.
|
||||
impl From<BrokerMessage> for Result<Option<u16>, ProtocolError> { |
||||
fn from(msg: BrokerMessage) -> Self { |
||||
if !msg.is_response() { |
||||
panic!("BrokerMessage is not a response"); |
||||
} |
||||
//let partial: u16 = ProtocolError::PartialContent.into();
|
||||
let res = msg.result(); |
||||
if res == 0 || ProtocolError::try_from(res).unwrap().is_stream() { |
||||
if msg.is_overlay() { |
||||
match msg.response_block() { |
||||
Some(_) => Ok(Some(res)), |
||||
None => Ok(None), |
||||
} |
||||
} else { |
||||
Ok(None) |
||||
} |
||||
} else { |
||||
Err(ProtocolError::try_from(res).unwrap()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Option represents if a Block is available. returns a clone.
|
||||
impl From<BrokerMessage> for Result<Option<Block>, ProtocolError> { |
||||
fn from(msg: BrokerMessage) -> Self { |
||||
if !msg.is_response() { |
||||
panic!("BrokerMessage is not a response"); |
||||
} |
||||
//let partial: u16 = ProtocolError::PartialContent.into();
|
||||
let res = msg.result(); |
||||
if res == 0 || ProtocolError::try_from(res).unwrap().is_stream() { |
||||
if msg.is_overlay() { |
||||
match msg.response_block() { |
||||
Some(b) => Ok(Some(b.clone())), |
||||
None => Ok(None), |
||||
} |
||||
} else { |
||||
Ok(None) |
||||
} |
||||
} else { |
||||
Err(ProtocolError::try_from(res).unwrap()) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
pub mod types; |
||||
|
||||
pub mod errors; |
||||
|
||||
pub mod broker_connection; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,20 @@ |
||||
[package] |
||||
name = "p2p-repo" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "P2P repository module of NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[dependencies] |
||||
blake3 = "1.3.1" |
||||
chacha20 = "0.9.0" |
||||
ed25519-dalek = "1.0.1" |
||||
rand = "0.7" |
||||
serde = { version = "1.0.142", features = ["derive"] } |
||||
serde_bare = "0.5.0" |
||||
serde_bytes = "0.11.7" |
||||
fastbloom-rs = "0.3.1" |
||||
debug_print = "1.0.0" |
||||
hex = "0.4.3" |
@ -0,0 +1,116 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Immutable Block
|
||||
|
||||
use crate::types::*; |
||||
|
||||
impl BlockV0 { |
||||
pub fn new( |
||||
children: Vec<BlockId>, |
||||
deps: ObjectDeps, |
||||
expiry: Option<Timestamp>, |
||||
content: Vec<u8>, |
||||
key: Option<SymKey>, |
||||
) -> BlockV0 { |
||||
let mut b = BlockV0 { |
||||
id: None, |
||||
key, |
||||
children, |
||||
deps, |
||||
expiry, |
||||
content, |
||||
}; |
||||
let block = Block::V0(b.clone()); |
||||
b.id = Some(block.get_id()); |
||||
b |
||||
} |
||||
} |
||||
|
||||
impl Block { |
||||
pub fn new( |
||||
children: Vec<BlockId>, |
||||
deps: ObjectDeps, |
||||
expiry: Option<Timestamp>, |
||||
content: Vec<u8>, |
||||
key: Option<SymKey>, |
||||
) -> Block { |
||||
Block::V0(BlockV0::new(children, deps, expiry, content, key)) |
||||
} |
||||
|
||||
/// Compute the ID
|
||||
pub fn get_id(&self) -> BlockId { |
||||
let ser = serde_bare::to_vec(self).unwrap(); |
||||
let hash = blake3::hash(ser.as_slice()); |
||||
Digest::Blake3Digest32(hash.as_bytes().clone()) |
||||
} |
||||
|
||||
/// Get the already computed ID
|
||||
pub fn id(&self) -> BlockId { |
||||
match self { |
||||
Block::V0(b) => match b.id { |
||||
Some(id) => id, |
||||
None => self.get_id(), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/// Get the content
|
||||
pub fn content(&self) -> &Vec<u8> { |
||||
match self { |
||||
Block::V0(b) => &b.content, |
||||
} |
||||
} |
||||
|
||||
/// Get the children
|
||||
pub fn children(&self) -> &Vec<BlockId> { |
||||
match self { |
||||
Block::V0(b) => &b.children, |
||||
} |
||||
} |
||||
|
||||
/// Get the dependencies
|
||||
pub fn deps(&self) -> &ObjectDeps { |
||||
match self { |
||||
Block::V0(b) => &b.deps, |
||||
} |
||||
} |
||||
|
||||
/// Get the expiry
|
||||
pub fn expiry(&self) -> Option<Timestamp> { |
||||
match self { |
||||
Block::V0(b) => b.expiry, |
||||
} |
||||
} |
||||
|
||||
pub fn set_expiry(&mut self, expiry: Option<Timestamp>) { |
||||
match self { |
||||
Block::V0(b) => { |
||||
b.id = None; |
||||
b.expiry = expiry |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Get the key
|
||||
pub fn key(&self) -> Option<SymKey> { |
||||
match self { |
||||
Block::V0(b) => b.key, |
||||
} |
||||
} |
||||
|
||||
/// Set the key
|
||||
pub fn set_key(&mut self, key: Option<SymKey>) { |
||||
match self { |
||||
Block::V0(b) => b.key = key, |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,581 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Branch of a Repository
|
||||
|
||||
use debug_print::*; |
||||
use std::collections::{HashMap, HashSet}; |
||||
|
||||
use fastbloom_rs::{BloomFilter as Filter, Membership}; |
||||
|
||||
use crate::object::*; |
||||
use crate::store::*; |
||||
use crate::types::*; |
||||
|
||||
impl MemberV0 { |
||||
/// New member
|
||||
pub fn new(id: PubKey, commit_types: Vec<CommitType>, metadata: Vec<u8>) -> MemberV0 { |
||||
MemberV0 { |
||||
id, |
||||
commit_types, |
||||
metadata, |
||||
} |
||||
} |
||||
|
||||
/// Check whether this member has permission for the given commit type
|
||||
pub fn has_perm(&self, commit_type: CommitType) -> bool { |
||||
self.commit_types.contains(&commit_type) |
||||
} |
||||
} |
||||
|
||||
impl Member { |
||||
/// New member
|
||||
pub fn new(id: PubKey, commit_types: Vec<CommitType>, metadata: Vec<u8>) -> Member { |
||||
Member::V0(MemberV0::new(id, commit_types, metadata)) |
||||
} |
||||
|
||||
/// Check whether this member has permission for the given commit type
|
||||
pub fn has_perm(&self, commit_type: CommitType) -> bool { |
||||
match self { |
||||
Member::V0(m) => m.has_perm(commit_type), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl BranchV0 { |
||||
pub fn new( |
||||
id: PubKey, |
||||
topic: PubKey, |
||||
secret: SymKey, |
||||
members: Vec<MemberV0>, |
||||
quorum: HashMap<CommitType, u32>, |
||||
ack_delay: RelTime, |
||||
tags: Vec<u8>, |
||||
metadata: Vec<u8>, |
||||
) -> BranchV0 { |
||||
BranchV0 { |
||||
id, |
||||
topic, |
||||
secret, |
||||
members, |
||||
quorum, |
||||
ack_delay, |
||||
tags, |
||||
metadata, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Branch { |
||||
pub fn new( |
||||
id: PubKey, |
||||
topic: PubKey, |
||||
secret: SymKey, |
||||
members: Vec<MemberV0>, |
||||
quorum: HashMap<CommitType, u32>, |
||||
ack_delay: RelTime, |
||||
tags: Vec<u8>, |
||||
metadata: Vec<u8>, |
||||
) -> Branch { |
||||
Branch::V0(BranchV0::new( |
||||
id, topic, secret, members, quorum, ack_delay, tags, metadata, |
||||
)) |
||||
} |
||||
|
||||
/// Get member by ID
|
||||
pub fn get_member(&self, id: &PubKey) -> Option<&MemberV0> { |
||||
match self { |
||||
Branch::V0(b) => { |
||||
for m in b.members.iter() { |
||||
if m.id == *id { |
||||
return Some(m); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
None |
||||
} |
||||
|
||||
/// Branch sync request from another peer
|
||||
///
|
||||
/// Return ObjectIds to send
|
||||
pub fn sync_req( |
||||
our_heads: &[ObjectId], |
||||
their_heads: &[ObjectId], |
||||
their_filter: &BloomFilter, |
||||
store: &impl RepoStore, |
||||
) -> Result<Vec<ObjectId>, ObjectParseError> { |
||||
//debug_println!(">> sync_req");
|
||||
//debug_println!(" our_heads: {:?}", our_heads);
|
||||
//debug_println!(" their_heads: {:?}", their_heads);
|
||||
|
||||
/// Load `Commit` `Object`s of a `Branch` from the `RepoStore` starting from the given `Object`,
|
||||
/// and collect `ObjectId`s starting from `our_heads` towards `their_heads`
|
||||
fn load_branch( |
||||
cobj: &Object, |
||||
store: &impl RepoStore, |
||||
their_heads: &[ObjectId], |
||||
visited: &mut HashSet<ObjectId>, |
||||
missing: &mut HashSet<ObjectId>, |
||||
) -> Result<bool, ObjectParseError> { |
||||
//debug_println!(">>> load_branch: {}", cobj.id());
|
||||
let id = cobj.id(); |
||||
|
||||
// root has no deps
|
||||
let is_root = cobj.deps().len() == 0; |
||||
//debug_println!(" deps: {:?}", cobj.deps());
|
||||
|
||||
// check if this commit object is present in their_heads
|
||||
let mut their_head_found = their_heads.contains(&id); |
||||
|
||||
// load deps, stop at the root or if this is a commit object from their_heads
|
||||
if !is_root && !their_head_found { |
||||
visited.insert(id); |
||||
for id in cobj.deps() { |
||||
match Object::load(*id, None, store) { |
||||
Ok(o) => { |
||||
if !visited.contains(id) { |
||||
if load_branch(&o, store, their_heads, visited, missing)? { |
||||
their_head_found = true; |
||||
} |
||||
} |
||||
} |
||||
Err(ObjectParseError::MissingBlocks(m)) => { |
||||
missing.extend(m); |
||||
} |
||||
Err(e) => return Err(e), |
||||
} |
||||
} |
||||
} |
||||
Ok(their_head_found) |
||||
} |
||||
|
||||
// missing commits from our branch
|
||||
let mut missing = HashSet::new(); |
||||
// our commits
|
||||
let mut ours = HashSet::new(); |
||||
// their commits
|
||||
let mut theirs = HashSet::new(); |
||||
|
||||
// collect all commits reachable from our_heads
|
||||
// up to the root or until encountering a commit from their_heads
|
||||
for id in our_heads { |
||||
let cobj = Object::load(*id, None, store)?; |
||||
let mut visited = HashSet::new(); |
||||
let their_head_found = |
||||
load_branch(&cobj, store, their_heads, &mut visited, &mut missing)?; |
||||
//debug_println!("<<< load_branch: {}", their_head_found);
|
||||
ours.extend(visited); // add if one of their_heads found
|
||||
} |
||||
|
||||
// collect all commits reachable from their_heads
|
||||
for id in their_heads { |
||||
let cobj = Object::load(*id, None, store)?; |
||||
let mut visited = HashSet::new(); |
||||
let their_head_found = load_branch(&cobj, store, &[], &mut visited, &mut missing)?; |
||||
//debug_println!("<<< load_branch: {}", their_head_found);
|
||||
theirs.extend(visited); // add if one of their_heads found
|
||||
} |
||||
|
||||
let mut result = &ours - &theirs; |
||||
|
||||
//debug_println!("!! ours: {:?}", ours);
|
||||
//debug_println!("!! theirs: {:?}", theirs);
|
||||
//debug_println!("!! result: {:?}", result);
|
||||
|
||||
// remove their_commits from result
|
||||
let filter = Filter::from_u8_array(their_filter.f.as_slice(), their_filter.k.into()); |
||||
for id in result.clone() { |
||||
match id { |
||||
Digest::Blake3Digest32(d) => { |
||||
if filter.contains(&d) { |
||||
result.remove(&id); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
//debug_println!("!! result filtered: {:?}", result);
|
||||
Ok(Vec::from_iter(result)) |
||||
} |
||||
} |
||||
|
||||
mod test { |
||||
use std::collections::HashMap; |
||||
|
||||
use ed25519_dalek::*; |
||||
use fastbloom_rs::{BloomFilter as Filter, FilterBuilder, Membership}; |
||||
use rand::rngs::OsRng; |
||||
|
||||
use crate::branch::*; |
||||
use crate::commit::*; |
||||
use crate::object::*; |
||||
use crate::repo; |
||||
use crate::store::*; |
||||
|
||||
#[test] |
||||
pub fn test_branch() { |
||||
fn add_obj( |
||||
content: ObjectContent, |
||||
deps: Vec<ObjectId>, |
||||
expiry: Option<Timestamp>, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let max_object_size = 4000; |
||||
let obj = Object::new( |
||||
content, |
||||
deps, |
||||
expiry, |
||||
max_object_size, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
); |
||||
println!(">>> add_obj"); |
||||
println!(" id: {:?}", obj.id()); |
||||
println!(" deps: {:?}", obj.deps()); |
||||
obj.save(store).unwrap(); |
||||
obj.reference().unwrap() |
||||
} |
||||
|
||||
fn add_commit( |
||||
branch: ObjectRef, |
||||
author_privkey: PrivKey, |
||||
author_pubkey: PubKey, |
||||
seq: u32, |
||||
deps: Vec<ObjectRef>, |
||||
acks: Vec<ObjectRef>, |
||||
body_ref: ObjectRef, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let mut obj_deps: Vec<ObjectId> = vec![]; |
||||
obj_deps.extend(deps.iter().map(|r| r.id)); |
||||
obj_deps.extend(acks.iter().map(|r| r.id)); |
||||
|
||||
let obj_ref = ObjectRef { |
||||
id: ObjectId::Blake3Digest32([1; 32]), |
||||
key: SymKey::ChaCha20Key([2; 32]), |
||||
}; |
||||
let refs = vec![obj_ref]; |
||||
let metadata = vec![5u8; 55]; |
||||
let expiry = None; |
||||
|
||||
let commit = Commit::new( |
||||
author_privkey, |
||||
author_pubkey, |
||||
seq, |
||||
branch, |
||||
deps, |
||||
acks, |
||||
refs, |
||||
metadata, |
||||
body_ref, |
||||
expiry, |
||||
) |
||||
.unwrap(); |
||||
//println!("commit: {:?}", commit);
|
||||
add_obj( |
||||
ObjectContent::Commit(commit), |
||||
obj_deps, |
||||
expiry, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
store, |
||||
) |
||||
} |
||||
|
||||
fn add_body_branch( |
||||
branch: Branch, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let deps = vec![]; |
||||
let expiry = None; |
||||
let body = CommitBody::Branch(branch); |
||||
//println!("body: {:?}", body);
|
||||
add_obj( |
||||
ObjectContent::CommitBody(body), |
||||
deps, |
||||
expiry, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
store, |
||||
) |
||||
} |
||||
|
||||
fn add_body_trans( |
||||
deps: Vec<ObjectId>, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let expiry = None; |
||||
let content = [7u8; 777].to_vec(); |
||||
let body = CommitBody::Transaction(Transaction::V0(content)); |
||||
//println!("body: {:?}", body);
|
||||
add_obj( |
||||
ObjectContent::CommitBody(body), |
||||
deps, |
||||
expiry, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
store, |
||||
) |
||||
} |
||||
|
||||
fn add_body_ack( |
||||
deps: Vec<ObjectId>, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
store: &mut impl RepoStore, |
||||
) -> ObjectRef { |
||||
let expiry = None; |
||||
let body = CommitBody::Ack(Ack::V0()); |
||||
//println!("body: {:?}", body);
|
||||
add_obj( |
||||
ObjectContent::CommitBody(body), |
||||
deps, |
||||
expiry, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
store, |
||||
) |
||||
} |
||||
|
||||
let mut store = HashMapRepoStore::new(); |
||||
let mut rng = OsRng {}; |
||||
|
||||
// repo
|
||||
|
||||
let repo_keypair: Keypair = Keypair::generate(&mut rng); |
||||
println!( |
||||
"repo private key: ({}) {:?}", |
||||
repo_keypair.secret.as_bytes().len(), |
||||
repo_keypair.secret.as_bytes() |
||||
); |
||||
println!( |
||||
"repo public key: ({}) {:?}", |
||||
repo_keypair.public.as_bytes().len(), |
||||
repo_keypair.public.as_bytes() |
||||
); |
||||
let _repo_privkey = PrivKey::Ed25519PrivKey(repo_keypair.secret.to_bytes()); |
||||
let repo_pubkey = PubKey::Ed25519PubKey(repo_keypair.public.to_bytes()); |
||||
let repo_secret = SymKey::ChaCha20Key([9; 32]); |
||||
|
||||
// branch
|
||||
|
||||
let branch_keypair: Keypair = Keypair::generate(&mut rng); |
||||
println!("branch public key: {:?}", branch_keypair.public.as_bytes()); |
||||
let branch_pubkey = PubKey::Ed25519PubKey(branch_keypair.public.to_bytes()); |
||||
|
||||
let member_keypair: Keypair = Keypair::generate(&mut rng); |
||||
println!("member public key: {:?}", member_keypair.public.as_bytes()); |
||||
let member_privkey = PrivKey::Ed25519PrivKey(member_keypair.secret.to_bytes()); |
||||
let member_pubkey = PubKey::Ed25519PubKey(member_keypair.public.to_bytes()); |
||||
|
||||
let metadata = [66u8; 64].to_vec(); |
||||
let commit_types = vec![CommitType::Ack, CommitType::Transaction]; |
||||
let secret = SymKey::ChaCha20Key([0; 32]); |
||||
|
||||
let member = MemberV0::new(member_pubkey, commit_types, metadata.clone()); |
||||
let members = vec![member]; |
||||
let mut quorum = HashMap::new(); |
||||
quorum.insert(CommitType::Transaction, 3); |
||||
let ack_delay = RelTime::Minutes(3); |
||||
let tags = [99u8; 32].to_vec(); |
||||
let branch = Branch::new( |
||||
branch_pubkey, |
||||
branch_pubkey, |
||||
secret, |
||||
members, |
||||
quorum, |
||||
ack_delay, |
||||
tags, |
||||
metadata, |
||||
); |
||||
//println!("branch: {:?}", branch);
|
||||
|
||||
fn print_branch() { |
||||
println!("branch deps/acks:"); |
||||
println!(""); |
||||
println!(" br"); |
||||
println!(" / \\"); |
||||
println!(" t1 t2"); |
||||
println!(" / \\ / \\"); |
||||
println!(" a3 t4<--t5-->(t1)"); |
||||
println!(" / \\"); |
||||
println!(" a6 a7"); |
||||
println!(""); |
||||
} |
||||
|
||||
print_branch(); |
||||
|
||||
// commit bodies
|
||||
|
||||
let branch_body = add_body_branch( |
||||
branch.clone(), |
||||
repo_pubkey.clone(), |
||||
repo_secret.clone(), |
||||
&mut store, |
||||
); |
||||
let ack_body = add_body_ack(vec![], repo_pubkey, repo_secret, &mut store); |
||||
let trans_body = add_body_trans(vec![], repo_pubkey, repo_secret, &mut store); |
||||
|
||||
// create & add commits to store
|
||||
|
||||
println!(">> br"); |
||||
let br = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
0, |
||||
vec![], |
||||
vec![], |
||||
branch_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> t1"); |
||||
let t1 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
1, |
||||
vec![br], |
||||
vec![], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> t2"); |
||||
let t2 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
2, |
||||
vec![br], |
||||
vec![], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> a3"); |
||||
let a3 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
3, |
||||
vec![t1], |
||||
vec![], |
||||
ack_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> t4"); |
||||
let t4 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
4, |
||||
vec![t2], |
||||
vec![t1], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> t5"); |
||||
let t5 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
5, |
||||
vec![t1, t2], |
||||
vec![t4], |
||||
trans_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> a6"); |
||||
let a6 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
6, |
||||
vec![t4], |
||||
vec![], |
||||
ack_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
println!(">> a7"); |
||||
let a7 = add_commit( |
||||
branch_body, |
||||
member_privkey, |
||||
member_pubkey, |
||||
7, |
||||
vec![t4], |
||||
vec![], |
||||
ack_body, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
&mut store, |
||||
); |
||||
|
||||
let c7 = Commit::load(a7, &store).unwrap(); |
||||
c7.verify(&branch, &store).unwrap(); |
||||
|
||||
let mut filter = Filter::new(FilterBuilder::new(10, 0.01)); |
||||
for commit_ref in [br, t1, t2, a3, t5, a6] { |
||||
match commit_ref.id { |
||||
ObjectId::Blake3Digest32(d) => filter.add(&d), |
||||
} |
||||
} |
||||
let cfg = filter.config(); |
||||
let their_commits = BloomFilter { |
||||
k: cfg.hashes, |
||||
f: filter.get_u8_array().to_vec(), |
||||
}; |
||||
|
||||
print_branch(); |
||||
println!(">> sync_req"); |
||||
println!(" our_heads: [a3, t5, a6, a7]"); |
||||
println!(" their_heads: [a3, t5]"); |
||||
println!(" their_commits: [br, t1, t2, a3, t5, a6]"); |
||||
|
||||
let ids = Branch::sync_req( |
||||
&[a3.id, t5.id, a6.id, a7.id], |
||||
&[a3.id, t5.id], |
||||
&their_commits, |
||||
&store, |
||||
) |
||||
.unwrap(); |
||||
|
||||
assert_eq!(ids.len(), 1); |
||||
assert!(ids.contains(&a7.id)); |
||||
} |
||||
} |
@ -0,0 +1,64 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use crate::store::{StorageError}; |
||||
|
||||
pub trait BrokerStore { |
||||
/// Load a property from the store.
|
||||
fn get(&self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<Vec<u8>, StorageError>; |
||||
|
||||
/// Load all the values of a property from the store.
|
||||
fn get_all( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
) -> Result<Vec<Vec<u8>>, StorageError>; |
||||
|
||||
/// Check if a specific value exists for a property from the store.
|
||||
fn has_property_value( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError>; |
||||
|
||||
/// Save a property value to the store.
|
||||
fn put( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError>; |
||||
|
||||
/// Replace the property of a key (single value) to the store.
|
||||
fn replace( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError>; |
||||
|
||||
/// Delete a property from the store.
|
||||
fn del(&self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<(), StorageError>; |
||||
|
||||
/// Delete all properties of a key from the store.
|
||||
fn del_all(&self, prefix: u8, key: &Vec<u8>, all_suffixes: &[u8]) -> Result<(), StorageError>; |
||||
|
||||
/// Delete a specific value for a property from the store.
|
||||
fn del_property_value( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError>; |
||||
} |
@ -0,0 +1,454 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Commit
|
||||
|
||||
use debug_print::*; |
||||
use ed25519_dalek::*; |
||||
|
||||
use std::collections::HashSet; |
||||
use std::iter::FromIterator; |
||||
|
||||
use crate::object::*; |
||||
use crate::store::*; |
||||
use crate::types::*; |
||||
|
||||
#[derive(Debug)] |
||||
pub enum CommitLoadError { |
||||
MissingBlocks(Vec<BlockId>), |
||||
ObjectParseError, |
||||
DeserializeError, |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
pub enum CommitVerifyError { |
||||
InvalidSignature, |
||||
PermissionDenied, |
||||
BodyLoadError(CommitLoadError), |
||||
DepLoadError(CommitLoadError), |
||||
} |
||||
impl CommitBody { |
||||
/// Get CommitType corresponding to CommitBody
|
||||
pub fn to_type(&self) -> CommitType { |
||||
match self { |
||||
CommitBody::Ack(_) => CommitType::Ack, |
||||
CommitBody::AddBranch(_) => CommitType::AddBranch, |
||||
CommitBody::AddMembers(_) => CommitType::AddMembers, |
||||
CommitBody::Branch(_) => CommitType::Branch, |
||||
CommitBody::EndOfBranch(_) => CommitType::EndOfBranch, |
||||
CommitBody::RemoveBranch(_) => CommitType::RemoveBranch, |
||||
CommitBody::Repository(_) => CommitType::Repository, |
||||
CommitBody::Snapshot(_) => CommitType::Snapshot, |
||||
CommitBody::Transaction(_) => CommitType::Transaction, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl CommitV0 { |
||||
/// New commit
|
||||
pub fn new( |
||||
author_privkey: PrivKey, |
||||
author_pubkey: PubKey, |
||||
seq: u32, |
||||
branch: ObjectRef, |
||||
deps: Vec<ObjectRef>, |
||||
acks: Vec<ObjectRef>, |
||||
refs: Vec<ObjectRef>, |
||||
metadata: Vec<u8>, |
||||
body: ObjectRef, |
||||
expiry: Option<Timestamp>, |
||||
) -> Result<CommitV0, SignatureError> { |
||||
let content = CommitContentV0 { |
||||
author: author_pubkey, |
||||
seq, |
||||
branch, |
||||
deps, |
||||
acks, |
||||
refs, |
||||
metadata, |
||||
body, |
||||
expiry, |
||||
}; |
||||
let content_ser = serde_bare::to_vec(&content).unwrap(); |
||||
|
||||
// sign commit
|
||||
let kp = match (author_privkey, author_pubkey) { |
||||
(PrivKey::Ed25519PrivKey(sk), PubKey::Ed25519PubKey(pk)) => [sk, pk].concat(), |
||||
}; |
||||
let keypair = Keypair::from_bytes(kp.as_slice())?; |
||||
let sig_bytes = keypair.sign(content_ser.as_slice()).to_bytes(); |
||||
let mut it = sig_bytes.chunks_exact(32); |
||||
let mut ss: Ed25519Sig = [[0; 32], [0; 32]]; |
||||
ss[0].copy_from_slice(it.next().unwrap()); |
||||
ss[1].copy_from_slice(it.next().unwrap()); |
||||
let sig = Sig::Ed25519Sig(ss); |
||||
Ok(CommitV0 { |
||||
content, |
||||
sig, |
||||
id: None, |
||||
key: None, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
impl Commit { |
||||
/// New commit
|
||||
pub fn new( |
||||
author_privkey: PrivKey, |
||||
author_pubkey: PubKey, |
||||
seq: u32, |
||||
branch: ObjectRef, |
||||
deps: Vec<ObjectRef>, |
||||
acks: Vec<ObjectRef>, |
||||
refs: Vec<ObjectRef>, |
||||
metadata: Vec<u8>, |
||||
body: ObjectRef, |
||||
expiry: Option<Timestamp>, |
||||
) -> Result<Commit, SignatureError> { |
||||
CommitV0::new( |
||||
author_privkey, |
||||
author_pubkey, |
||||
seq, |
||||
branch, |
||||
deps, |
||||
acks, |
||||
refs, |
||||
metadata, |
||||
body, |
||||
expiry, |
||||
) |
||||
.map(|c| Commit::V0(c)) |
||||
} |
||||
|
||||
/// Load commit from store
|
||||
pub fn load(commit_ref: ObjectRef, store: &impl RepoStore) -> Result<Commit, CommitLoadError> { |
||||
let (id, key) = (commit_ref.id, commit_ref.key); |
||||
match Object::load(id, Some(key), store) { |
||||
Ok(obj) => { |
||||
let content = obj |
||||
.content() |
||||
.map_err(|_e| CommitLoadError::ObjectParseError)?; |
||||
let mut commit = match content { |
||||
ObjectContent::Commit(c) => c, |
||||
_ => return Err(CommitLoadError::DeserializeError), |
||||
}; |
||||
commit.set_id(id); |
||||
commit.set_key(key); |
||||
Ok(commit) |
||||
} |
||||
Err(ObjectParseError::MissingBlocks(missing)) => { |
||||
Err(CommitLoadError::MissingBlocks(missing)) |
||||
} |
||||
Err(_) => Err(CommitLoadError::ObjectParseError), |
||||
} |
||||
} |
||||
|
||||
/// Load commit body from store
|
||||
pub fn load_body(&self, store: &impl RepoStore) -> Result<CommitBody, CommitLoadError> { |
||||
let content = self.content(); |
||||
let (id, key) = (content.body.id, content.body.key); |
||||
let obj = Object::load(id.clone(), Some(key.clone()), store).map_err(|e| match e { |
||||
ObjectParseError::MissingBlocks(missing) => CommitLoadError::MissingBlocks(missing), |
||||
_ => CommitLoadError::ObjectParseError, |
||||
})?; |
||||
let content = obj |
||||
.content() |
||||
.map_err(|_e| CommitLoadError::ObjectParseError)?; |
||||
match content { |
||||
ObjectContent::CommitBody(body) => Ok(body), |
||||
_ => Err(CommitLoadError::DeserializeError), |
||||
} |
||||
} |
||||
|
||||
/// Get ID of parent `Object`
|
||||
pub fn id(&self) -> Option<ObjectId> { |
||||
match self { |
||||
Commit::V0(c) => c.id, |
||||
} |
||||
} |
||||
|
||||
/// Set ID of parent `Object`
|
||||
pub fn set_id(&mut self, id: ObjectId) { |
||||
match self { |
||||
Commit::V0(c) => c.id = Some(id), |
||||
} |
||||
} |
||||
|
||||
/// Get key of parent `Object`
|
||||
pub fn key(&self) -> Option<SymKey> { |
||||
match self { |
||||
Commit::V0(c) => c.key, |
||||
} |
||||
} |
||||
|
||||
/// Set key of parent `Object`
|
||||
pub fn set_key(&mut self, key: SymKey) { |
||||
match self { |
||||
Commit::V0(c) => c.key = Some(key), |
||||
} |
||||
} |
||||
|
||||
/// Get commit signature
|
||||
pub fn sig(&self) -> &Sig { |
||||
match self { |
||||
Commit::V0(c) => &c.sig, |
||||
} |
||||
} |
||||
|
||||
/// Get commit content
|
||||
pub fn content(&self) -> &CommitContentV0 { |
||||
match self { |
||||
Commit::V0(c) => &c.content, |
||||
} |
||||
} |
||||
|
||||
/// Get acks
|
||||
pub fn acks(&self) -> Vec<ObjectRef> { |
||||
match self { |
||||
Commit::V0(c) => c.content.acks.clone(), |
||||
} |
||||
} |
||||
|
||||
/// Get deps
|
||||
pub fn deps(&self) -> Vec<ObjectRef> { |
||||
match self { |
||||
Commit::V0(c) => c.content.deps.clone(), |
||||
} |
||||
} |
||||
|
||||
/// Get all direct commit dependencies of the commit (`deps`, `acks`)
|
||||
pub fn deps_acks(&self) -> Vec<ObjectRef> { |
||||
match self { |
||||
Commit::V0(c) => [c.content.acks.clone(), c.content.deps.clone()].concat(), |
||||
} |
||||
} |
||||
|
||||
/// Get seq
|
||||
pub fn seq(&self) -> u32 { |
||||
match self { |
||||
Commit::V0(c) => c.content.seq, |
||||
} |
||||
} |
||||
|
||||
/// Verify commit signature
|
||||
pub fn verify_sig(&self) -> Result<(), SignatureError> { |
||||
let c = match self { |
||||
Commit::V0(c) => c, |
||||
}; |
||||
let content_ser = serde_bare::to_vec(&c.content).unwrap(); |
||||
let pubkey = match c.content.author { |
||||
PubKey::Ed25519PubKey(pk) => pk, |
||||
}; |
||||
let pk = PublicKey::from_bytes(&pubkey)?; |
||||
let sig_bytes = match c.sig { |
||||
Sig::Ed25519Sig(ss) => [ss[0], ss[1]].concat(), |
||||
}; |
||||
let sig = Signature::from_bytes(&sig_bytes)?; |
||||
pk.verify_strict(&content_ser, &sig) |
||||
} |
||||
|
||||
/// Verify commit permissions
|
||||
pub fn verify_perm(&self, body: &CommitBody, branch: &Branch) -> Result<(), CommitVerifyError> { |
||||
let content = self.content(); |
||||
match branch.get_member(&content.author) { |
||||
Some(m) => { |
||||
if m.has_perm(body.to_type()) { |
||||
return Ok(()); |
||||
} |
||||
} |
||||
None => (), |
||||
} |
||||
Err(CommitVerifyError::PermissionDenied) |
||||
} |
||||
|
||||
/// Verify if the commit's `body` and dependencies (`deps` & `acks`) are available in the `store`
|
||||
pub fn verify_deps(&self, store: &impl RepoStore) -> Result<Vec<ObjectId>, CommitLoadError> { |
||||
//debug_println!(">> verify_deps: #{}", self.seq());
|
||||
/// Load `Commit`s of a `Branch` from the `RepoStore` starting from the given `Commit`,
|
||||
/// and collect missing `ObjectId`s
|
||||
fn load_branch( |
||||
commit: &Commit, |
||||
store: &impl RepoStore, |
||||
visited: &mut HashSet<ObjectId>, |
||||
missing: &mut HashSet<ObjectId>, |
||||
) -> Result<(), CommitLoadError> { |
||||
//debug_println!(">>> load_branch: #{}", commit.seq());
|
||||
// the commit verify_deps() was called on may not have an ID set,
|
||||
// but the commits loaded from store should have it
|
||||
match commit.id() { |
||||
Some(id) => { |
||||
if visited.contains(&id) { |
||||
return Ok(()); |
||||
} |
||||
visited.insert(id); |
||||
} |
||||
None => (), |
||||
} |
||||
|
||||
// load body & check if it's the Branch commit at the root
|
||||
let is_root = match commit.load_body(store) { |
||||
Ok(body) => body.to_type() == CommitType::Branch, |
||||
Err(CommitLoadError::MissingBlocks(m)) => { |
||||
missing.extend(m); |
||||
false |
||||
} |
||||
Err(e) => return Err(e), |
||||
}; |
||||
debug_println!("!!! is_root: {}", is_root); |
||||
|
||||
// load deps
|
||||
if !is_root { |
||||
for dep in commit.deps_acks() { |
||||
match Commit::load(dep, store) { |
||||
Ok(c) => { |
||||
load_branch(&c, store, visited, missing)?; |
||||
} |
||||
Err(CommitLoadError::MissingBlocks(m)) => { |
||||
missing.extend(m); |
||||
} |
||||
Err(e) => return Err(e), |
||||
} |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
let mut visited = HashSet::new(); |
||||
let mut missing = HashSet::new(); |
||||
load_branch(self, store, &mut visited, &mut missing)?; |
||||
|
||||
if !missing.is_empty() { |
||||
return Err(CommitLoadError::MissingBlocks(Vec::from_iter(missing))); |
||||
} |
||||
Ok(Vec::from_iter(visited)) |
||||
} |
||||
|
||||
/// Verify signature, permissions, and dependencies
|
||||
pub fn verify(&self, branch: &Branch, store: &impl RepoStore) -> Result<(), CommitVerifyError> { |
||||
self.verify_sig() |
||||
.map_err(|_e| CommitVerifyError::InvalidSignature)?; |
||||
let body = self |
||||
.load_body(store) |
||||
.map_err(|e| CommitVerifyError::BodyLoadError(e))?; |
||||
self.verify_perm(&body, branch)?; |
||||
self.verify_deps(store) |
||||
.map_err(|e| CommitVerifyError::DepLoadError(e))?; |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
mod test { |
||||
use std::collections::HashMap; |
||||
|
||||
use ed25519_dalek::*; |
||||
use rand::rngs::OsRng; |
||||
|
||||
use crate::branch::*; |
||||
use crate::commit::*; |
||||
use crate::store::*; |
||||
use crate::types::*; |
||||
|
||||
#[test] |
||||
pub fn test_commit() { |
||||
let mut csprng = OsRng {}; |
||||
let keypair: Keypair = Keypair::generate(&mut csprng); |
||||
println!( |
||||
"private key: ({}) {:?}", |
||||
keypair.secret.as_bytes().len(), |
||||
keypair.secret.as_bytes() |
||||
); |
||||
println!( |
||||
"public key: ({}) {:?}", |
||||
keypair.public.as_bytes().len(), |
||||
keypair.public.as_bytes() |
||||
); |
||||
let ed_priv_key = keypair.secret.to_bytes(); |
||||
let ed_pub_key = keypair.public.to_bytes(); |
||||
let priv_key = PrivKey::Ed25519PrivKey(ed_priv_key); |
||||
let pub_key = PubKey::Ed25519PubKey(ed_pub_key); |
||||
let seq = 3; |
||||
let obj_ref = ObjectRef { |
||||
id: ObjectId::Blake3Digest32([1; 32]), |
||||
key: SymKey::ChaCha20Key([2; 32]), |
||||
}; |
||||
let obj_refs = vec![obj_ref]; |
||||
let branch = obj_ref.clone(); |
||||
let deps = obj_refs.clone(); |
||||
let acks = obj_refs.clone(); |
||||
let refs = obj_refs.clone(); |
||||
let metadata = vec![1, 2, 3]; |
||||
let body_ref = obj_ref.clone(); |
||||
let expiry = Some(2342); |
||||
|
||||
let commit = Commit::new( |
||||
priv_key, pub_key, seq, branch, deps, acks, refs, metadata, body_ref, expiry, |
||||
) |
||||
.unwrap(); |
||||
println!("commit: {:?}", commit); |
||||
|
||||
let store = HashMapRepoStore::new(); |
||||
let metadata = [66u8; 64].to_vec(); |
||||
let commit_types = vec![CommitType::Ack, CommitType::Transaction]; |
||||
let key: [u8; 32] = [0; 32]; |
||||
let secret = SymKey::ChaCha20Key(key); |
||||
let member = MemberV0::new(pub_key, commit_types, metadata.clone()); |
||||
let members = vec![member]; |
||||
let mut quorum = HashMap::new(); |
||||
quorum.insert(CommitType::Transaction, 3); |
||||
let ack_delay = RelTime::Minutes(3); |
||||
let tags = [99u8; 32].to_vec(); |
||||
let branch = Branch::new( |
||||
pub_key.clone(), |
||||
pub_key.clone(), |
||||
secret, |
||||
members, |
||||
quorum, |
||||
ack_delay, |
||||
tags, |
||||
metadata, |
||||
); |
||||
//println!("branch: {:?}", branch);
|
||||
let body = CommitBody::Ack(Ack::V0()); |
||||
//println!("body: {:?}", body);
|
||||
|
||||
match commit.load_body(&store) { |
||||
Ok(_b) => panic!("Body should not exist"), |
||||
Err(CommitLoadError::MissingBlocks(missing)) => { |
||||
assert_eq!(missing.len(), 1); |
||||
} |
||||
Err(e) => panic!("Commit verify error: {:?}", e), |
||||
} |
||||
|
||||
let content = commit.content(); |
||||
println!("content: {:?}", content); |
||||
|
||||
commit.verify_sig().expect("Invalid signature"); |
||||
commit |
||||
.verify_perm(&body, &branch) |
||||
.expect("Permission denied"); |
||||
|
||||
match commit.verify_deps(&store) { |
||||
Ok(_) => panic!("Commit should not be Ok"), |
||||
Err(CommitLoadError::MissingBlocks(missing)) => { |
||||
assert_eq!(missing.len(), 1); |
||||
} |
||||
Err(e) => panic!("Commit verify error: {:?}", e), |
||||
} |
||||
|
||||
match commit.verify(&branch, &store) { |
||||
Ok(_) => panic!("Commit should not be Ok"), |
||||
Err(CommitVerifyError::BodyLoadError(CommitLoadError::MissingBlocks(missing))) => { |
||||
assert_eq!(missing.len(), 1); |
||||
} |
||||
Err(e) => panic!("Commit verify error: {:?}", e), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Errors
|
||||
|
||||
pub enum NgError { |
||||
InvalidSignature, |
||||
SerializationError, |
||||
} |
||||
|
||||
impl From<serde_bare::error::Error> for NgError { |
||||
fn from(e: serde_bare::error::Error) -> Self { |
||||
NgError::SerializationError |
||||
} |
||||
} |
||||
|
||||
impl From<ed25519_dalek::ed25519::Error> for NgError { |
||||
fn from(e: ed25519_dalek::ed25519::Error) -> Self { |
||||
NgError::InvalidSignature |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
pub mod types; |
||||
|
||||
pub mod store; |
||||
|
||||
pub mod block; |
||||
|
||||
pub mod object; |
||||
|
||||
pub mod commit; |
||||
|
||||
pub mod branch; |
||||
|
||||
pub mod repo; |
||||
|
||||
pub mod utils; |
||||
|
||||
pub mod errors; |
||||
|
||||
pub mod broker_store; |
@ -0,0 +1,909 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Merkle hash tree of Objects
|
||||
|
||||
use std::collections::{HashMap, HashSet}; |
||||
|
||||
use debug_print::*; |
||||
|
||||
use chacha20::cipher::{KeyIvInit, StreamCipher}; |
||||
use chacha20::ChaCha20; |
||||
|
||||
use crate::store::*; |
||||
use crate::types::*; |
||||
|
||||
/// Size of a serialized empty Block
|
||||
const EMPTY_BLOCK_SIZE: usize = 12; |
||||
/// Size of a serialized BlockId
|
||||
const BLOCK_ID_SIZE: usize = 33; |
||||
/// Size of serialized SymKey
|
||||
const BLOCK_KEY_SIZE: usize = 33; |
||||
/// Size of serialized Object with deps reference.
|
||||
const EMPTY_ROOT_SIZE_DEPSREF: usize = 77; |
||||
/// Extra size needed if depsRef used instead of deps list.
|
||||
const DEPSREF_OVERLOAD: usize = EMPTY_ROOT_SIZE_DEPSREF - EMPTY_BLOCK_SIZE; |
||||
/// Varint extra bytes when reaching the maximum value we will ever use
|
||||
const BIG_VARINT_EXTRA: usize = 3; |
||||
/// Varint extra bytes when reaching the maximum size of data byte arrays.
|
||||
const DATA_VARINT_EXTRA: usize = 4; |
||||
/// Max extra space used by the deps list
|
||||
const MAX_DEPS_SIZE: usize = 8 * BLOCK_ID_SIZE; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct Object { |
||||
/// Blocks of the Object (nodes of the tree)
|
||||
blocks: Vec<Block>, |
||||
|
||||
/// Dependencies
|
||||
deps: Vec<ObjectId>, |
||||
} |
||||
|
||||
/// Object parsing errors
|
||||
#[derive(Debug)] |
||||
pub enum ObjectParseError { |
||||
/// Missing blocks
|
||||
MissingBlocks(Vec<BlockId>), |
||||
/// Missing root key
|
||||
MissingRootKey, |
||||
/// Invalid BlockId encountered in the tree
|
||||
InvalidBlockId, |
||||
/// Too many or too few children of a block
|
||||
InvalidChildren, |
||||
/// Number of keys does not match number of children of a block
|
||||
InvalidKeys, |
||||
/// Invalid DepList object content
|
||||
InvalidDeps, |
||||
/// Error deserializing content of a block
|
||||
BlockDeserializeError, |
||||
/// Error deserializing content of the object
|
||||
ObjectDeserializeError, |
||||
} |
||||
|
||||
/// Object copy error
|
||||
#[derive(Debug)] |
||||
pub enum ObjectCopyError { |
||||
NotFound, |
||||
ParseError, |
||||
} |
||||
|
||||
impl Object { |
||||
fn convergence_key(repo_pubkey: PubKey, repo_secret: SymKey) -> [u8; blake3::OUT_LEN] { |
||||
let key_material = match (repo_pubkey, repo_secret) { |
||||
(PubKey::Ed25519PubKey(pubkey), SymKey::ChaCha20Key(secret)) => { |
||||
[pubkey, secret].concat() |
||||
} |
||||
}; |
||||
blake3::derive_key("NextGraph Data BLAKE3 key", key_material.as_slice()) |
||||
} |
||||
|
||||
fn make_block( |
||||
content: &[u8], |
||||
conv_key: &[u8; blake3::OUT_LEN], |
||||
children: Vec<ObjectId>, |
||||
deps: ObjectDeps, |
||||
expiry: Option<Timestamp>, |
||||
) -> Block { |
||||
let key_hash = blake3::keyed_hash(conv_key, content); |
||||
let nonce = [0u8; 12]; |
||||
let key = key_hash.as_bytes(); |
||||
let mut cipher = ChaCha20::new(key.into(), &nonce.into()); |
||||
let mut content_enc = Vec::from(content); |
||||
let mut content_enc_slice = &mut content_enc.as_mut_slice(); |
||||
cipher.apply_keystream(&mut content_enc_slice); |
||||
let key = SymKey::ChaCha20Key(key.clone()); |
||||
let block = Block::new(children, deps, expiry, content_enc, Some(key)); |
||||
//debug_println!(">>> make_block:");
|
||||
//debug_println!("!! id: {:?}", obj.id());
|
||||
//debug_println!("!! children: ({}) {:?}", children.len(), children);
|
||||
block |
||||
} |
||||
|
||||
fn make_deps( |
||||
deps_vec: Vec<ObjectId>, |
||||
object_size: usize, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
) -> ObjectDeps { |
||||
if deps_vec.len() <= 8 { |
||||
ObjectDeps::ObjectIdList(deps_vec) |
||||
} else { |
||||
let dep_list = DepList::V0(deps_vec); |
||||
let dep_obj = Object::new( |
||||
ObjectContent::DepList(dep_list), |
||||
vec![], |
||||
None, |
||||
object_size, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
); |
||||
let dep_ref = ObjectRef { |
||||
id: dep_obj.id(), |
||||
key: dep_obj.key().unwrap(), |
||||
}; |
||||
ObjectDeps::DepListRef(dep_ref) |
||||
} |
||||
} |
||||
|
||||
/// Build tree from leaves, returns parent nodes
|
||||
fn make_tree( |
||||
leaves: &[Block], |
||||
conv_key: &ChaCha20Key, |
||||
root_deps: &ObjectDeps, |
||||
expiry: Option<Timestamp>, |
||||
arity: usize, |
||||
) -> Vec<Block> { |
||||
let mut parents = vec![]; |
||||
let chunks = leaves.chunks(arity); |
||||
let mut it = chunks.peekable(); |
||||
while let Some(nodes) = it.next() { |
||||
let keys = nodes.iter().map(|block| block.key().unwrap()).collect(); |
||||
let children = nodes.iter().map(|block| block.id()).collect(); |
||||
let content = BlockContentV0::InternalNode(keys); |
||||
let content_ser = serde_bare::to_vec(&content).unwrap(); |
||||
let child_deps = ObjectDeps::ObjectIdList(vec![]); |
||||
let deps = if parents.is_empty() && it.peek().is_none() { |
||||
root_deps.clone() |
||||
} else { |
||||
child_deps |
||||
}; |
||||
parents.push(Self::make_block( |
||||
content_ser.as_slice(), |
||||
conv_key, |
||||
children, |
||||
deps, |
||||
expiry, |
||||
)); |
||||
} |
||||
//debug_println!("parents += {}", parents.len());
|
||||
|
||||
if 1 < parents.len() { |
||||
let mut great_parents = |
||||
Self::make_tree(parents.as_slice(), conv_key, root_deps, expiry, arity); |
||||
parents.append(&mut great_parents); |
||||
} |
||||
parents |
||||
} |
||||
|
||||
/// Create new Object from given content
|
||||
///
|
||||
/// The Object is chunked and stored in a Merkle tree
|
||||
/// The arity of the Merkle tree is the maximum that fits in the given `max_object_size`
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `content`: Object content
|
||||
/// * `deps`: Dependencies of the object
|
||||
/// * `block_size`: Desired block size for chunking content, rounded up to nearest valid block size
|
||||
/// * `repo_pubkey`: Repository public key
|
||||
/// * `repo_secret`: Repository secret
|
||||
pub fn new( |
||||
content: ObjectContent, |
||||
deps: Vec<ObjectId>, |
||||
expiry: Option<Timestamp>, |
||||
block_size: usize, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
) -> Object { |
||||
// create blocks by chunking + encrypting content
|
||||
let valid_block_size = store_valid_value_size(block_size); |
||||
let data_chunk_size = valid_block_size - EMPTY_BLOCK_SIZE - DATA_VARINT_EXTRA; |
||||
|
||||
let mut blocks: Vec<Block> = vec![]; |
||||
let conv_key = Self::convergence_key(repo_pubkey, repo_secret); |
||||
|
||||
let obj_deps = Self::make_deps(deps.clone(), valid_block_size, repo_pubkey, repo_secret); |
||||
|
||||
let content_ser = serde_bare::to_vec(&content).unwrap(); |
||||
|
||||
if EMPTY_BLOCK_SIZE + DATA_VARINT_EXTRA + BLOCK_ID_SIZE * deps.len() + content_ser.len() |
||||
<= valid_block_size |
||||
{ |
||||
// content fits in root node
|
||||
let data_chunk = BlockContentV0::DataChunk(content_ser.clone()); |
||||
let content_ser = serde_bare::to_vec(&data_chunk).unwrap(); |
||||
blocks.push(Self::make_block( |
||||
content_ser.as_slice(), |
||||
&conv_key, |
||||
vec![], |
||||
obj_deps, |
||||
expiry, |
||||
)); |
||||
} else { |
||||
// chunk content and create leaf nodes
|
||||
for chunk in content_ser.chunks(data_chunk_size) { |
||||
let data_chunk = BlockContentV0::DataChunk(chunk.to_vec()); |
||||
let content_ser = serde_bare::to_vec(&data_chunk).unwrap(); |
||||
blocks.push(Self::make_block( |
||||
content_ser.as_slice(), |
||||
&conv_key, |
||||
vec![], |
||||
ObjectDeps::ObjectIdList(vec![]), |
||||
expiry, |
||||
)); |
||||
} |
||||
|
||||
// internal nodes
|
||||
// arity: max number of ObjectRefs that fit inside an InternalNode Object within the object_size limit
|
||||
let arity: usize = |
||||
(valid_block_size - EMPTY_BLOCK_SIZE - BIG_VARINT_EXTRA * 2 - MAX_DEPS_SIZE) |
||||
/ (BLOCK_ID_SIZE + BLOCK_KEY_SIZE); |
||||
let mut parents = |
||||
Self::make_tree(blocks.as_slice(), &conv_key, &obj_deps, expiry, arity); |
||||
blocks.append(&mut parents); |
||||
} |
||||
|
||||
Object { blocks, deps } |
||||
} |
||||
|
||||
pub fn copy( |
||||
&self, |
||||
expiry: Option<Timestamp>, |
||||
repo_pubkey: PubKey, |
||||
repo_secret: SymKey, |
||||
) -> Result<Object, ObjectCopyError> { |
||||
// getting the old object from store
|
||||
let leaves: Vec<Block> = self.leaves().map_err(|_e| ObjectCopyError::ParseError)?; |
||||
|
||||
let conv_key = Self::convergence_key(repo_pubkey, repo_secret); |
||||
let block_size = leaves.first().unwrap().content().len(); |
||||
let valid_block_size = store_valid_value_size(block_size); |
||||
|
||||
let mut blocks: Vec<Block> = vec![]; |
||||
for block in leaves { |
||||
let mut copy = block.clone(); |
||||
copy.set_expiry(expiry); |
||||
blocks.push(copy); |
||||
} |
||||
|
||||
// internal nodes
|
||||
// arity: max number of ObjectRefs that fit inside an InternalNode Object within the object_size limit
|
||||
let arity: usize = |
||||
(valid_block_size - EMPTY_BLOCK_SIZE - BIG_VARINT_EXTRA * 2 - MAX_DEPS_SIZE) |
||||
/ (BLOCK_ID_SIZE + BLOCK_KEY_SIZE); |
||||
let mut parents = Self::make_tree( |
||||
blocks.as_slice(), |
||||
&conv_key, |
||||
self.root().deps(), |
||||
expiry, |
||||
arity, |
||||
); |
||||
blocks.append(&mut parents); |
||||
|
||||
Ok(Object { |
||||
blocks, |
||||
deps: self.deps().clone(), |
||||
}) |
||||
} |
||||
|
||||
/// Load an Object from RepoStore
|
||||
///
|
||||
/// Returns Ok(Object) or an Err(Vec<ObjectId>) of missing BlockIds
|
||||
pub fn load( |
||||
id: ObjectId, |
||||
key: Option<SymKey>, |
||||
store: &impl RepoStore, |
||||
) -> Result<Object, ObjectParseError> { |
||||
fn load_tree( |
||||
parents: Vec<BlockId>, |
||||
store: &impl RepoStore, |
||||
blocks: &mut Vec<Block>, |
||||
missing: &mut Vec<BlockId>, |
||||
) { |
||||
let mut children: Vec<BlockId> = vec![]; |
||||
for id in parents { |
||||
match store.get(&id) { |
||||
Ok(block) => { |
||||
blocks.insert(0, block.clone()); |
||||
match block { |
||||
Block::V0(o) => { |
||||
children.extend(o.children.iter().rev()); |
||||
} |
||||
} |
||||
} |
||||
Err(_) => missing.push(id.clone()), |
||||
} |
||||
} |
||||
if !children.is_empty() { |
||||
load_tree(children, store, blocks, missing); |
||||
} |
||||
} |
||||
|
||||
let mut blocks: Vec<Block> = vec![]; |
||||
let mut missing: Vec<BlockId> = vec![]; |
||||
|
||||
load_tree(vec![id], store, &mut blocks, &mut missing); |
||||
|
||||
if !missing.is_empty() { |
||||
return Err(ObjectParseError::MissingBlocks(missing)); |
||||
} |
||||
|
||||
let root = blocks.last_mut().unwrap(); |
||||
if key.is_some() { |
||||
root.set_key(key); |
||||
} |
||||
|
||||
let deps = match root.deps().clone() { |
||||
ObjectDeps::ObjectIdList(deps_vec) => deps_vec, |
||||
ObjectDeps::DepListRef(deps_ref) => { |
||||
let obj = Object::load(deps_ref.id, Some(deps_ref.key), store)?; |
||||
match obj.content()? { |
||||
ObjectContent::DepList(DepList::V0(deps_vec)) => deps_vec, |
||||
_ => return Err(ObjectParseError::InvalidDeps), |
||||
} |
||||
} |
||||
}; |
||||
|
||||
Ok(Object { blocks, deps }) |
||||
} |
||||
|
||||
/// Save blocks of the object in the store
|
||||
pub fn save(&self, store: &mut impl RepoStore) -> Result<(), StorageError> { |
||||
let mut deduplicated: HashSet<ObjectId> = HashSet::new(); |
||||
for block in &self.blocks { |
||||
let id = block.id(); |
||||
if deduplicated.get(&id).is_none() { |
||||
store.put(block)?; |
||||
deduplicated.insert(id); |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Get the ID of the Object
|
||||
pub fn id(&self) -> ObjectId { |
||||
self.blocks.last().unwrap().id() |
||||
} |
||||
|
||||
/// Get the key for the Object
|
||||
pub fn key(&self) -> Option<SymKey> { |
||||
self.blocks.last().unwrap().key() |
||||
} |
||||
|
||||
/// Get an `ObjectRef` for the root object
|
||||
pub fn reference(&self) -> Option<ObjectRef> { |
||||
if self.key().is_some() { |
||||
Some(ObjectRef { |
||||
id: self.id(), |
||||
key: self.key().unwrap(), |
||||
}) |
||||
} else { |
||||
None |
||||
} |
||||
} |
||||
|
||||
pub fn root(&self) -> &Block { |
||||
self.blocks.last().unwrap() |
||||
} |
||||
|
||||
pub fn expiry(&self) -> Option<Timestamp> { |
||||
self.blocks.last().unwrap().expiry() |
||||
} |
||||
|
||||
pub fn deps(&self) -> &Vec<ObjectId> { |
||||
&self.deps |
||||
} |
||||
|
||||
pub fn blocks(&self) -> &Vec<Block> { |
||||
&self.blocks |
||||
} |
||||
|
||||
pub fn to_hashmap(&self) -> HashMap<BlockId, Block> { |
||||
let mut map: HashMap<BlockId, Block> = HashMap::new(); |
||||
for block in &self.blocks { |
||||
map.insert(block.id(), block.clone()); |
||||
} |
||||
map |
||||
} |
||||
|
||||
/// Collect leaves from the tree
|
||||
fn collect_leaves( |
||||
blocks: &Vec<Block>, |
||||
parents: &Vec<(ObjectId, SymKey)>, |
||||
parent_index: usize, |
||||
leaves: &mut Option<&mut Vec<Block>>, |
||||
obj_content: &mut Option<&mut Vec<u8>>, |
||||
) -> Result<(), ObjectParseError> { |
||||
/*debug_println!(
|
||||
">>> collect_leaves: #{}..{}", |
||||
parent_index, |
||||
parent_index + parents.len() - 1 |
||||
);*/ |
||||
let mut children: Vec<(ObjectId, SymKey)> = vec![]; |
||||
let mut i = parent_index; |
||||
|
||||
for (id, key) in parents { |
||||
//debug_println!("!!! parent: #{}", i);
|
||||
let block = &blocks[i]; |
||||
i += 1; |
||||
|
||||
// verify object ID
|
||||
if *id != block.id() { |
||||
debug_println!("Invalid ObjectId.\nExp: {:?}\nGot: {:?}", *id, block.id()); |
||||
return Err(ObjectParseError::InvalidBlockId); |
||||
} |
||||
|
||||
match block { |
||||
Block::V0(b) => { |
||||
// decrypt content
|
||||
let mut content_dec = b.content.clone(); |
||||
match key { |
||||
SymKey::ChaCha20Key(key) => { |
||||
let nonce = [0u8; 12]; |
||||
let mut cipher = ChaCha20::new(key.into(), &nonce.into()); |
||||
let mut content_dec_slice = &mut content_dec.as_mut_slice(); |
||||
cipher.apply_keystream(&mut content_dec_slice); |
||||
} |
||||
} |
||||
|
||||
// deserialize content
|
||||
let content: BlockContentV0; |
||||
match serde_bare::from_slice(content_dec.as_slice()) { |
||||
Ok(c) => content = c, |
||||
Err(e) => { |
||||
debug_println!("Block deserialize error: {}", e); |
||||
return Err(ObjectParseError::BlockDeserializeError); |
||||
} |
||||
} |
||||
|
||||
// parse content
|
||||
match content { |
||||
BlockContentV0::InternalNode(keys) => { |
||||
if keys.len() != b.children.len() { |
||||
debug_println!( |
||||
"Invalid keys length: got {}, expected {}", |
||||
keys.len(), |
||||
b.children.len() |
||||
); |
||||
debug_println!("!!! children: {:?}", b.children); |
||||
debug_println!("!!! keys: {:?}", keys); |
||||
return Err(ObjectParseError::InvalidKeys); |
||||
} |
||||
|
||||
for (id, key) in b.children.iter().zip(keys.iter()) { |
||||
children.push((id.clone(), key.clone())); |
||||
} |
||||
} |
||||
BlockContentV0::DataChunk(chunk) => { |
||||
if leaves.is_some() { |
||||
let mut leaf = block.clone(); |
||||
leaf.set_key(Some(*key)); |
||||
let l = &mut **leaves.as_mut().unwrap(); |
||||
l.push(leaf); |
||||
} |
||||
if obj_content.is_some() { |
||||
let c = &mut **obj_content.as_mut().unwrap(); |
||||
c.extend_from_slice(chunk.as_slice()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
if !children.is_empty() { |
||||
if parent_index < children.len() { |
||||
return Err(ObjectParseError::InvalidChildren); |
||||
} |
||||
match Self::collect_leaves( |
||||
blocks, |
||||
&children, |
||||
parent_index - children.len(), |
||||
leaves, |
||||
obj_content, |
||||
) { |
||||
Ok(_) => (), |
||||
Err(e) => return Err(e), |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Parse the Object and return the leaf Blocks with decryption key set
|
||||
pub fn leaves(&self) -> Result<Vec<Block>, ObjectParseError> { |
||||
let mut leaves: Vec<Block> = vec![]; |
||||
let parents = vec![(self.id(), self.key().unwrap())]; |
||||
match Self::collect_leaves( |
||||
&self.blocks, |
||||
&parents, |
||||
self.blocks.len() - 1, |
||||
&mut Some(&mut leaves), |
||||
&mut None, |
||||
) { |
||||
Ok(_) => Ok(leaves), |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
|
||||
/// Parse the Object and return the decrypted content assembled from Blocks
|
||||
pub fn content(&self) -> Result<ObjectContent, ObjectParseError> { |
||||
if self.key().is_none() { |
||||
return Err(ObjectParseError::MissingRootKey); |
||||
} |
||||
let mut obj_content: Vec<u8> = vec![]; |
||||
let parents = vec![(self.id(), self.key().unwrap())]; |
||||
match Self::collect_leaves( |
||||
&self.blocks, |
||||
&parents, |
||||
self.blocks.len() - 1, |
||||
&mut None, |
||||
&mut Some(&mut obj_content), |
||||
) { |
||||
Ok(_) => { |
||||
let content: ObjectContent; |
||||
match serde_bare::from_slice(obj_content.as_slice()) { |
||||
Ok(c) => Ok(c), |
||||
Err(e) => { |
||||
debug_println!("Object deserialize error: {}", e); |
||||
Err(ObjectParseError::ObjectDeserializeError) |
||||
} |
||||
} |
||||
} |
||||
Err(e) => Err(e), |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod test { |
||||
|
||||
use crate::object::*; |
||||
use crate::store::*; |
||||
use crate::types::*; |
||||
|
||||
// Those constants are calculated with RepoStore::get_max_value_size
|
||||
|
||||
/// Maximum arity of branch containing max number of leaves
|
||||
const MAX_ARITY_LEAVES: usize = 31774; |
||||
/// Maximum arity of root branch
|
||||
const MAX_ARITY_ROOT: usize = 31770; |
||||
/// Maximum data that can fit in object.content
|
||||
const MAX_DATA_PAYLOAD_SIZE: usize = 2097112; |
||||
|
||||
/// Test tree API
|
||||
#[test] |
||||
pub fn test_object() { |
||||
let file = File::V0(FileV0 { |
||||
content_type: Vec::from("file/test"), |
||||
metadata: Vec::from("some meta data here"), |
||||
content: [(0..255).collect::<Vec<u8>>().as_slice(); 320].concat(), |
||||
}); |
||||
let content = ObjectContent::File(file); |
||||
|
||||
let deps: Vec<ObjectId> = vec![Digest::Blake3Digest32([9; 32])]; |
||||
let exp = Some(2u32.pow(31)); |
||||
let max_object_size = 0; |
||||
|
||||
let repo_secret = SymKey::ChaCha20Key([0; 32]); |
||||
let repo_pubkey = PubKey::Ed25519PubKey([1; 32]); |
||||
|
||||
let obj = Object::new( |
||||
content.clone(), |
||||
deps.clone(), |
||||
exp, |
||||
max_object_size, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
); |
||||
|
||||
println!("obj.id: {:?}", obj.id()); |
||||
println!("obj.key: {:?}", obj.key()); |
||||
println!("obj.deps: {:?}", obj.deps()); |
||||
println!("obj.blocks.len: {:?}", obj.blocks().len()); |
||||
|
||||
let mut i = 0; |
||||
for node in obj.blocks() { |
||||
println!("#{}: {:?}", i, node.id()); |
||||
i += 1; |
||||
} |
||||
|
||||
assert_eq!(*obj.deps(), deps); |
||||
|
||||
match obj.content() { |
||||
Ok(cnt) => { |
||||
assert_eq!(content, cnt); |
||||
} |
||||
Err(e) => panic!("Object parse error: {:?}", e), |
||||
} |
||||
let mut store = HashMapRepoStore::new(); |
||||
|
||||
obj.save(&mut store).expect("Object save error"); |
||||
|
||||
let obj2 = Object::load(obj.id(), obj.key(), &store).unwrap(); |
||||
|
||||
println!("obj2.id: {:?}", obj2.id()); |
||||
println!("obj2.key: {:?}", obj2.key()); |
||||
println!("obj2.deps: {:?}", obj2.deps()); |
||||
println!("obj2.blocks.len: {:?}", obj2.blocks().len()); |
||||
let mut i = 0; |
||||
for node in obj2.blocks() { |
||||
println!("#{}: {:?}", i, node.id()); |
||||
i += 1; |
||||
} |
||||
|
||||
assert_eq!(*obj2.deps(), deps); |
||||
assert_eq!(*obj2.deps(), deps); |
||||
|
||||
match obj2.content() { |
||||
Ok(cnt) => { |
||||
assert_eq!(content, cnt); |
||||
} |
||||
Err(e) => panic!("Object2 parse error: {:?}", e), |
||||
} |
||||
|
||||
let obj3 = Object::load(obj.id(), None, &store).unwrap(); |
||||
|
||||
println!("obj3.id: {:?}", obj3.id()); |
||||
println!("obj3.key: {:?}", obj3.key()); |
||||
println!("obj3.deps: {:?}", obj3.deps()); |
||||
println!("obj3.blocks.len: {:?}", obj3.blocks().len()); |
||||
let mut i = 0; |
||||
for node in obj3.blocks() { |
||||
println!("#{}: {:?}", i, node.id()); |
||||
i += 1; |
||||
} |
||||
|
||||
assert_eq!(*obj3.deps(), deps); |
||||
|
||||
match obj3.content() { |
||||
Err(ObjectParseError::MissingRootKey) => (), |
||||
Err(e) => panic!("Object3 parse error: {:?}", e), |
||||
Ok(_) => panic!("Object3 should not return content"), |
||||
} |
||||
|
||||
let exp4 = Some(2342); |
||||
let obj4 = obj.copy(exp4, repo_pubkey, repo_secret).unwrap(); |
||||
obj4.save(&mut store).unwrap(); |
||||
|
||||
assert_eq!(obj4.expiry(), exp4); |
||||
assert_eq!(*obj.deps(), deps); |
||||
|
||||
match obj4.content() { |
||||
Ok(cnt) => { |
||||
assert_eq!(content, cnt); |
||||
} |
||||
Err(e) => panic!("Object3 parse error: {:?}", e), |
||||
} |
||||
} |
||||
|
||||
/// Checks that a content that fits the root node, will not be chunked into children nodes
|
||||
#[test] |
||||
pub fn test_depth_1() { |
||||
let deps: Vec<ObjectId> = vec![Digest::Blake3Digest32([9; 32])]; |
||||
|
||||
let empty_file = ObjectContent::File(File::V0(FileV0 { |
||||
content_type: vec![], |
||||
metadata: vec![], |
||||
content: vec![], |
||||
})); |
||||
let empty_file_ser = serde_bare::to_vec(&empty_file).unwrap(); |
||||
println!("empty file size: {}", empty_file_ser.len()); |
||||
|
||||
let size = store_max_value_size() |
||||
- EMPTY_BLOCK_SIZE |
||||
- DATA_VARINT_EXTRA |
||||
- BLOCK_ID_SIZE * deps.len() |
||||
- empty_file_ser.len() |
||||
- DATA_VARINT_EXTRA; |
||||
println!("file size: {}", size); |
||||
|
||||
let content = ObjectContent::File(File::V0(FileV0 { |
||||
content_type: vec![], |
||||
metadata: vec![], |
||||
content: vec![99; size], |
||||
})); |
||||
let content_ser = serde_bare::to_vec(&content).unwrap(); |
||||
println!("content len: {}", content_ser.len()); |
||||
|
||||
let expiry = Some(2u32.pow(31)); |
||||
let max_object_size = store_max_value_size(); |
||||
|
||||
let repo_secret = SymKey::ChaCha20Key([0; 32]); |
||||
let repo_pubkey = PubKey::Ed25519PubKey([1; 32]); |
||||
|
||||
let object = Object::new( |
||||
content, |
||||
deps, |
||||
expiry, |
||||
max_object_size, |
||||
repo_pubkey, |
||||
repo_secret, |
||||
); |
||||
|
||||
println!("root_id: {:?}", object.id()); |
||||
println!("root_key: {:?}", object.key().unwrap()); |
||||
println!("nodes.len: {:?}", object.blocks().len()); |
||||
//println!("root: {:?}", tree.root());
|
||||
//println!("nodes: {:?}", object.blocks);
|
||||
assert_eq!(object.blocks.len(), 1); |
||||
} |
||||
|
||||
#[test] |
||||
pub fn test_block_size() { |
||||
let max_block_size = store_max_value_size(); |
||||
println!("max_object_size: {}", max_block_size); |
||||
|
||||
let id = Digest::Blake3Digest32([0u8; 32]); |
||||
let key = SymKey::ChaCha20Key([0u8; 32]); |
||||
|
||||
let one_key = BlockContentV0::InternalNode(vec![key]); |
||||
let one_key_ser = serde_bare::to_vec(&one_key).unwrap(); |
||||
|
||||
let two_keys = BlockContentV0::InternalNode(vec![key, key]); |
||||
let two_keys_ser = serde_bare::to_vec(&two_keys).unwrap(); |
||||
|
||||
let max_keys = BlockContentV0::InternalNode(vec![key; MAX_ARITY_LEAVES]); |
||||
let max_keys_ser = serde_bare::to_vec(&max_keys).unwrap(); |
||||
|
||||
let data = BlockContentV0::DataChunk(vec![]); |
||||
let data_ser = serde_bare::to_vec(&data).unwrap(); |
||||
|
||||
let data_full = BlockContentV0::DataChunk(vec![0; MAX_DATA_PAYLOAD_SIZE]); |
||||
let data_full_ser = serde_bare::to_vec(&data_full).unwrap(); |
||||
|
||||
let leaf_empty = Block::new( |
||||
vec![], |
||||
ObjectDeps::ObjectIdList(vec![]), |
||||
Some(2342), |
||||
data_ser.clone(), |
||||
None, |
||||
); |
||||
let leaf_empty_ser = serde_bare::to_vec(&leaf_empty).unwrap(); |
||||
|
||||
let leaf_full_data = Block::new( |
||||
vec![], |
||||
ObjectDeps::ObjectIdList(vec![]), |
||||
Some(2342), |
||||
data_full_ser.clone(), |
||||
None, |
||||
); |
||||
let leaf_full_data_ser = serde_bare::to_vec(&leaf_full_data).unwrap(); |
||||
|
||||
let root_depsref = Block::new( |
||||
vec![], |
||||
ObjectDeps::DepListRef(ObjectRef { id: id, key: key }), |
||||
Some(2342), |
||||
data_ser.clone(), |
||||
None, |
||||
); |
||||
|
||||
let root_depsref_ser = serde_bare::to_vec(&root_depsref).unwrap(); |
||||
|
||||
let internal_max = Block::new( |
||||
vec![id; MAX_ARITY_LEAVES], |
||||
ObjectDeps::ObjectIdList(vec![]), |
||||
Some(2342), |
||||
max_keys_ser.clone(), |
||||
None, |
||||
); |
||||
let internal_max_ser = serde_bare::to_vec(&internal_max).unwrap(); |
||||
|
||||
let internal_one = Block::new( |
||||
vec![id; 1], |
||||
ObjectDeps::ObjectIdList(vec![]), |
||||
Some(2342), |
||||
one_key_ser.clone(), |
||||
None, |
||||
); |
||||
let internal_one_ser = serde_bare::to_vec(&internal_one).unwrap(); |
||||
|
||||
let internal_two = Block::new( |
||||
vec![id; 2], |
||||
ObjectDeps::ObjectIdList(vec![]), |
||||
Some(2342), |
||||
two_keys_ser.clone(), |
||||
None, |
||||
); |
||||
let internal_two_ser = serde_bare::to_vec(&internal_two).unwrap(); |
||||
|
||||
let root_one = Block::new( |
||||
vec![id; 1], |
||||
ObjectDeps::ObjectIdList(vec![id; 8]), |
||||
Some(2342), |
||||
one_key_ser.clone(), |
||||
None, |
||||
); |
||||
let root_one_ser = serde_bare::to_vec(&root_one).unwrap(); |
||||
|
||||
let root_two = Block::new( |
||||
vec![id; 2], |
||||
ObjectDeps::ObjectIdList(vec![id; 8]), |
||||
Some(2342), |
||||
two_keys_ser.clone(), |
||||
None, |
||||
); |
||||
let root_two_ser = serde_bare::to_vec(&root_two).unwrap(); |
||||
|
||||
println!( |
||||
"range of valid value sizes {} {}", |
||||
store_valid_value_size(0), |
||||
store_max_value_size() |
||||
); |
||||
|
||||
println!( |
||||
"max_data_payload_of_object: {}", |
||||
max_block_size - EMPTY_BLOCK_SIZE - DATA_VARINT_EXTRA |
||||
); |
||||
|
||||
println!( |
||||
"max_data_payload_depth_1: {}", |
||||
max_block_size - EMPTY_BLOCK_SIZE - DATA_VARINT_EXTRA - MAX_DEPS_SIZE |
||||
); |
||||
|
||||
println!( |
||||
"max_data_payload_depth_2: {}", |
||||
MAX_ARITY_ROOT * MAX_DATA_PAYLOAD_SIZE |
||||
); |
||||
|
||||
println!( |
||||
"max_data_payload_depth_3: {}", |
||||
MAX_ARITY_ROOT * MAX_ARITY_LEAVES * MAX_DATA_PAYLOAD_SIZE |
||||
); |
||||
|
||||
let max_arity_leaves = (max_block_size - EMPTY_BLOCK_SIZE - BIG_VARINT_EXTRA * 2) |
||||
/ (BLOCK_ID_SIZE + BLOCK_KEY_SIZE); |
||||
println!("max_arity_leaves: {}", max_arity_leaves); |
||||
assert_eq!(max_arity_leaves, MAX_ARITY_LEAVES); |
||||
assert_eq!( |
||||
max_block_size - EMPTY_BLOCK_SIZE - DATA_VARINT_EXTRA, |
||||
MAX_DATA_PAYLOAD_SIZE |
||||
); |
||||
let max_arity_root = |
||||
(max_block_size - EMPTY_BLOCK_SIZE - MAX_DEPS_SIZE - BIG_VARINT_EXTRA * 2) |
||||
/ (BLOCK_ID_SIZE + BLOCK_KEY_SIZE); |
||||
println!("max_arity_root: {}", max_arity_root); |
||||
assert_eq!(max_arity_root, MAX_ARITY_ROOT); |
||||
println!("store_max_value_size: {}", leaf_full_data_ser.len()); |
||||
assert_eq!(leaf_full_data_ser.len(), max_block_size); |
||||
println!("leaf_empty: {}", leaf_empty_ser.len()); |
||||
assert_eq!(leaf_empty_ser.len(), EMPTY_BLOCK_SIZE); |
||||
println!("root_depsref: {}", root_depsref_ser.len()); |
||||
assert_eq!(root_depsref_ser.len(), EMPTY_ROOT_SIZE_DEPSREF); |
||||
println!("internal_max: {}", internal_max_ser.len()); |
||||
assert_eq!( |
||||
internal_max_ser.len(), |
||||
EMPTY_BLOCK_SIZE |
||||
+ BIG_VARINT_EXTRA * 2 |
||||
+ MAX_ARITY_LEAVES * (BLOCK_ID_SIZE + BLOCK_KEY_SIZE) |
||||
); |
||||
assert!(internal_max_ser.len() < max_block_size); |
||||
println!("internal_one: {}", internal_one_ser.len()); |
||||
assert_eq!( |
||||
internal_one_ser.len(), |
||||
EMPTY_BLOCK_SIZE + 1 * BLOCK_ID_SIZE + 1 * BLOCK_KEY_SIZE |
||||
); |
||||
println!("internal_two: {}", internal_two_ser.len()); |
||||
assert_eq!( |
||||
internal_two_ser.len(), |
||||
EMPTY_BLOCK_SIZE + 2 * BLOCK_ID_SIZE + 2 * BLOCK_KEY_SIZE |
||||
); |
||||
println!("root_one: {}", root_one_ser.len()); |
||||
assert_eq!( |
||||
root_one_ser.len(), |
||||
EMPTY_BLOCK_SIZE + 8 * BLOCK_ID_SIZE + 1 * BLOCK_ID_SIZE + 1 * BLOCK_KEY_SIZE |
||||
); |
||||
println!("root_two: {}", root_two_ser.len()); |
||||
assert_eq!( |
||||
root_two_ser.len(), |
||||
EMPTY_BLOCK_SIZE + 8 * BLOCK_ID_SIZE + 2 * BLOCK_ID_SIZE + 2 * BLOCK_KEY_SIZE |
||||
); |
||||
|
||||
// let object_size_1 = 4096 * 1 - VALUE_HEADER_SIZE;
|
||||
// let object_size_512 = 4096 * MAX_PAGES_PER_VALUE - VALUE_HEADER_SIZE;
|
||||
// let arity_1: usize =
|
||||
// (object_size_1 - 8 * OBJECT_ID_SIZE) / (OBJECT_ID_SIZE + OBJECT_KEY_SIZE);
|
||||
// let arity_512: usize =
|
||||
// (object_size_512 - 8 * OBJECT_ID_SIZE) / (OBJECT_ID_SIZE + OBJECT_KEY_SIZE);
|
||||
|
||||
// println!("1-page object_size: {}", object_size_1);
|
||||
// println!("512-page object_size: {}", object_size_512);
|
||||
// println!("max arity of 1-page object: {}", arity_1);
|
||||
// println!("max arity of 512-page object: {}", arity_512);
|
||||
} |
||||
} |
@ -0,0 +1,46 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Repository
|
||||
|
||||
use crate::types::*; |
||||
|
||||
impl RepositoryV0 { |
||||
pub fn new( |
||||
id: &PubKey, |
||||
branches: &Vec<ObjectRef>, |
||||
allow_ext_requests: bool, |
||||
metadata: &Vec<u8>, |
||||
) -> RepositoryV0 { |
||||
RepositoryV0 { |
||||
id: id.clone(), |
||||
branches: branches.clone(), |
||||
allow_ext_requests, |
||||
metadata: metadata.clone(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Repository { |
||||
pub fn new( |
||||
id: &PubKey, |
||||
branches: &Vec<ObjectRef>, |
||||
allow_ext_requests: bool, |
||||
metadata: &Vec<u8>, |
||||
) -> Repository { |
||||
Repository::V0(RepositoryV0::new( |
||||
id, |
||||
branches, |
||||
allow_ext_requests, |
||||
metadata, |
||||
)) |
||||
} |
||||
} |
@ -0,0 +1,109 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! Block store
|
||||
|
||||
use crate::types::*; |
||||
|
||||
use std::{ |
||||
cmp::min, |
||||
collections::{hash_map::Iter, HashMap}, |
||||
mem::size_of_val, |
||||
}; |
||||
use std::sync::{Arc, RwLock}; |
||||
|
||||
pub trait RepoStore { |
||||
/// Load a block from the store.
|
||||
fn get(&self, id: &BlockId) -> Result<Block, StorageError>; |
||||
|
||||
/// Save a block to the store.
|
||||
fn put(&self, block: &Block) -> Result<BlockId, StorageError>; |
||||
|
||||
/// Delete a block from the store.
|
||||
fn del(&self, id: &BlockId) -> Result<(Block, usize), StorageError>; |
||||
} |
||||
|
||||
#[derive(Debug, PartialEq)] |
||||
pub enum StorageError { |
||||
NotFound, |
||||
InvalidValue, |
||||
BackendError, |
||||
SerializationError, |
||||
} |
||||
|
||||
impl From<serde_bare::error::Error> for StorageError { |
||||
fn from(e: serde_bare::error::Error) -> Self { |
||||
StorageError::SerializationError |
||||
} |
||||
} |
||||
|
||||
const MIN_SIZE: usize = 4072; |
||||
const PAGE_SIZE: usize = 4096; |
||||
const HEADER: usize = PAGE_SIZE - MIN_SIZE; |
||||
const MAX_FACTOR: usize = 512; |
||||
|
||||
/// Returns a valid/optimal value size for the entries of the storage backend.
|
||||
pub fn store_valid_value_size(size: usize) -> usize { |
||||
min( |
||||
((size + HEADER) as f32 / PAGE_SIZE as f32).ceil() as usize, |
||||
MAX_FACTOR, |
||||
) * PAGE_SIZE |
||||
- HEADER |
||||
} |
||||
|
||||
/// Returns the maximum value size for the entries of the storage backend.
|
||||
pub const fn store_max_value_size() -> usize { |
||||
MAX_FACTOR * PAGE_SIZE - HEADER |
||||
} |
||||
|
||||
/// Store with a HashMap backend
|
||||
pub struct HashMapRepoStore { |
||||
blocks: RwLock<HashMap<BlockId, Block>>, |
||||
} |
||||
|
||||
impl HashMapRepoStore { |
||||
pub fn new() -> HashMapRepoStore { |
||||
HashMapRepoStore { |
||||
blocks: RwLock::new(HashMap::new()), |
||||
} |
||||
} |
||||
|
||||
pub fn get_len(&self) -> usize { |
||||
self.blocks.read().unwrap().len() |
||||
} |
||||
|
||||
pub fn get_all(&self) -> Vec<Block> { |
||||
self.blocks.read().unwrap().values().map(|x| x.clone()).collect() |
||||
} |
||||
} |
||||
|
||||
impl RepoStore for HashMapRepoStore { |
||||
fn get(&self, id: &BlockId) -> Result<Block, StorageError> { |
||||
match self.blocks.read().unwrap().get(id) { |
||||
Some(block) => Ok(block.clone()), |
||||
None => Err(StorageError::NotFound), |
||||
} |
||||
} |
||||
|
||||
fn put(&self, block: &Block) -> Result<BlockId, StorageError> { |
||||
let id = block.id(); |
||||
let mut b = block.clone(); |
||||
b.set_key(None); |
||||
self.blocks.write().unwrap().insert(id, b); |
||||
Ok(id) |
||||
} |
||||
|
||||
fn del(&self, id: &BlockId) -> Result<(Block, usize), StorageError> { |
||||
let block = self.blocks.write().unwrap().remove(id).ok_or(StorageError::NotFound)?; |
||||
let size = size_of_val(&block); |
||||
Ok((block, size)) |
||||
} |
||||
} |
@ -0,0 +1,530 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
//! P2P Repo types
|
||||
//!
|
||||
//! Corresponds to the BARE schema
|
||||
|
||||
use core::fmt; |
||||
use serde::{Deserialize, Serialize}; |
||||
use std::collections::HashMap; |
||||
use std::hash::Hash; |
||||
|
||||
//
|
||||
// COMMON TYPES
|
||||
//
|
||||
|
||||
/// 32-byte Blake3 hash digest
|
||||
pub type Blake3Digest32 = [u8; 32]; |
||||
|
||||
/// Hash digest
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] |
||||
pub enum Digest { |
||||
Blake3Digest32(Blake3Digest32), |
||||
} |
||||
|
||||
impl fmt::Display for Digest { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
match self { |
||||
Digest::Blake3Digest32(d) => write!(f, "{}", hex::encode(d)), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// ChaCha20 symmetric key
|
||||
pub type ChaCha20Key = [u8; 32]; |
||||
|
||||
/// Symmetric cryptographic key
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] |
||||
pub enum SymKey { |
||||
ChaCha20Key(ChaCha20Key), |
||||
} |
||||
|
||||
impl SymKey { |
||||
pub fn slice(&self) -> &[u8; 32] { |
||||
match self { |
||||
SymKey::ChaCha20Key(o) => o, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Curve25519 public key
|
||||
pub type Ed25519PubKey = [u8; 32]; |
||||
|
||||
/// Curve25519 private key
|
||||
pub type Ed25519PrivKey = [u8; 32]; |
||||
|
||||
/// Public key
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] |
||||
pub enum PubKey { |
||||
Ed25519PubKey(Ed25519PubKey), |
||||
} |
||||
|
||||
impl PubKey { |
||||
pub fn slice(&self) -> &[u8; 32] { |
||||
match self { |
||||
PubKey::Ed25519PubKey(o) => o, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for PubKey { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
match self { |
||||
PubKey::Ed25519PubKey(d) => write!(f, "{}", hex::encode(d)), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Private key
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum PrivKey { |
||||
Ed25519PrivKey(Ed25519PrivKey), |
||||
} |
||||
|
||||
/// Ed25519 signature
|
||||
pub type Ed25519Sig = [[u8; 32]; 2]; |
||||
|
||||
/// Cryptographic signature
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Sig { |
||||
Ed25519Sig(Ed25519Sig), |
||||
} |
||||
|
||||
/// Timestamp: absolute time in minutes since 2022-02-22 22:22 UTC
|
||||
pub type Timestamp = u32; |
||||
|
||||
pub const EPOCH_AS_UNIX_TIMESTAMP: u64 = 1645568520; |
||||
|
||||
/// Relative time (e.g. delay from current time)
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum RelTime { |
||||
Seconds(u8), |
||||
Minutes(u8), |
||||
Hours(u8), |
||||
Days(u8), |
||||
} |
||||
|
||||
/// Bloom filter (variable size)
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct BloomFilter { |
||||
/// Number of hash functions
|
||||
pub k: u32, |
||||
|
||||
/// Filter
|
||||
#[serde(with = "serde_bytes")] |
||||
pub f: Vec<u8>, |
||||
} |
||||
|
||||
/// Bloom filter (128 B)
|
||||
///
|
||||
/// (m=1024; k=7; p=0.01; n=107)
|
||||
pub type BloomFilter128 = [[u8; 32]; 4]; |
||||
|
||||
/// Bloom filter (1 KiB)
|
||||
///
|
||||
/// (m=8192; k=7; p=0.01; n=855)
|
||||
pub type BloomFilter1K = [[u8; 32]; 32]; |
||||
|
||||
//
|
||||
// REPOSITORY TYPES
|
||||
//
|
||||
|
||||
/// Block ID:
|
||||
/// BLAKE3 hash over the serialized Object with encrypted content
|
||||
pub type BlockId = Digest; |
||||
|
||||
/// Block reference
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] |
||||
pub struct BlockRef { |
||||
/// Object ID
|
||||
pub id: BlockId, |
||||
|
||||
/// Key for decrypting the Object
|
||||
pub key: SymKey, |
||||
} |
||||
|
||||
/// Object ID
|
||||
pub type ObjectId = BlockId; |
||||
|
||||
/// Object reference
|
||||
pub type ObjectRef = BlockRef; |
||||
|
||||
/// Internal node of a Merkle tree
|
||||
pub type InternalNode = Vec<SymKey>; |
||||
|
||||
/// Content of BlockV0: a Merkle tree node
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum BlockContentV0 { |
||||
/// Internal node with references to children
|
||||
InternalNode(InternalNode), |
||||
|
||||
#[serde(with = "serde_bytes")] |
||||
DataChunk(Vec<u8>), |
||||
} |
||||
|
||||
/// List of ObjectId dependencies as encrypted Object content
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum DepList { |
||||
V0(Vec<ObjectId>), |
||||
} |
||||
|
||||
/// Dependencies of an Object
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum ObjectDeps { |
||||
/// List of Object IDs (max. 8),
|
||||
ObjectIdList(Vec<ObjectId>), |
||||
|
||||
/// Reference to an Object that contains a DepList
|
||||
DepListRef(ObjectRef), |
||||
} |
||||
|
||||
/// Immutable block with encrypted content
|
||||
///
|
||||
/// `ObjectContent` is chunked and stored as `Block`s in a Merkle tree.
|
||||
/// A Block is a Merkle tree node.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct BlockV0 { |
||||
/// Block ID
|
||||
#[serde(skip)] |
||||
pub id: Option<BlockId>, |
||||
|
||||
/// Block Key
|
||||
#[serde(skip)] |
||||
pub key: Option<SymKey>, |
||||
|
||||
/// Block IDs for child nodes in the Merkle tree
|
||||
pub children: Vec<BlockId>, |
||||
|
||||
/// Other objects this object depends on (e.g. Commit deps & acks)
|
||||
/// Only set for the root block
|
||||
pub deps: ObjectDeps, |
||||
|
||||
/// Expiry time of this object and all of its children
|
||||
/// when the object should be deleted by all replicas
|
||||
/// Only set for the root block
|
||||
pub expiry: Option<Timestamp>, |
||||
|
||||
/// Encrypted ObjectContentV0
|
||||
///
|
||||
/// Encrypted using convergent encryption with ChaCha20:
|
||||
/// - convergence_key: BLAKE3 derive_key ("NextGraph Data BLAKE3 key",
|
||||
/// repo_pubkey + repo_secret)
|
||||
/// - key: BLAKE3 keyed hash (convergence_key, plain_object_content)
|
||||
/// - nonce: 0
|
||||
#[serde(with = "serde_bytes")] |
||||
pub content: Vec<u8>, |
||||
} |
||||
|
||||
/// Immutable object with encrypted content
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Block { |
||||
V0(BlockV0), |
||||
} |
||||
|
||||
/// Repository definition
|
||||
///
|
||||
/// Published in root branch, where:
|
||||
/// - branch_pubkey: repo_pubkey
|
||||
/// - branch_secret: BLAKE3 derive_key ("NextGraph Root Branch secret",
|
||||
/// repo_pubkey + repo_secret)
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct RepositoryV0 { |
||||
/// Repo public key ID
|
||||
pub id: PubKey, |
||||
|
||||
/// List of branches
|
||||
pub branches: Vec<ObjectRef>, |
||||
|
||||
/// Whether or not to allow external requests
|
||||
pub allow_ext_requests: bool, |
||||
|
||||
/// App-specific metadata
|
||||
#[serde(with = "serde_bytes")] |
||||
pub metadata: Vec<u8>, |
||||
} |
||||
|
||||
/// Repository definition
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Repository { |
||||
V0(RepositoryV0), |
||||
} |
||||
|
||||
/// Add a branch to the repository
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum AddBranch { |
||||
V0(ObjectRef), |
||||
} |
||||
|
||||
/// Remove a branch from the repository
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum RemoveBranch { |
||||
V0(ObjectRef), |
||||
} |
||||
|
||||
/// Commit object types
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] |
||||
pub enum CommitType { |
||||
Repository, |
||||
AddBranch, |
||||
RemoveBranch, |
||||
Branch, |
||||
AddMembers, |
||||
EndOfBranch, |
||||
Transaction, |
||||
Snapshot, |
||||
Ack, |
||||
} |
||||
|
||||
/// Member of a Branch
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct MemberV0 { |
||||
/// Member public key ID
|
||||
pub id: PubKey, |
||||
|
||||
/// Commit types the member is allowed to publish in the branch
|
||||
pub commit_types: Vec<CommitType>, |
||||
|
||||
/// App-specific metadata
|
||||
/// (role, permissions, cryptographic material, etc)
|
||||
#[serde(with = "serde_bytes")] |
||||
pub metadata: Vec<u8>, |
||||
} |
||||
|
||||
/// Member of a branch
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Member { |
||||
V0(MemberV0), |
||||
} |
||||
|
||||
/// Branch definition
|
||||
///
|
||||
/// First commit in a branch, signed by branch key
|
||||
/// In case of a fork, the commit deps indicat
|
||||
/// the previous branch heads.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct BranchV0 { |
||||
/// Branch public key ID
|
||||
pub id: PubKey, |
||||
|
||||
/// Pub/sub topic for publishing events
|
||||
pub topic: PubKey, |
||||
|
||||
/// Branch secret key
|
||||
pub secret: SymKey, |
||||
|
||||
/// Members with permissions
|
||||
pub members: Vec<MemberV0>, |
||||
|
||||
/// Number of acks required for a commit to be valid
|
||||
pub quorum: HashMap<CommitType, u32>, |
||||
|
||||
/// Delay to send explicit acks,
|
||||
/// if not enough implicit acks arrived by then
|
||||
pub ack_delay: RelTime, |
||||
|
||||
/// Tags for organizing branches within the repository
|
||||
#[serde(with = "serde_bytes")] |
||||
pub tags: Vec<u8>, |
||||
|
||||
/// App-specific metadata (validation rules, etc)
|
||||
#[serde(with = "serde_bytes")] |
||||
pub metadata: Vec<u8>, |
||||
} |
||||
|
||||
/// Branch definition
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Branch { |
||||
V0(BranchV0), |
||||
} |
||||
|
||||
/// Add members to an existing branch
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct AddMembersV0 { |
||||
/// Members to add, with permissions
|
||||
pub members: Vec<MemberV0>, |
||||
|
||||
/// New quorum
|
||||
pub quorum: Option<HashMap<CommitType, u32>>, |
||||
|
||||
/// New ackDelay
|
||||
pub ack_delay: Option<RelTime>, |
||||
} |
||||
|
||||
/// Add members to an existing branch
|
||||
///
|
||||
/// If a member already exists, it overwrites the previous definition,
|
||||
/// in that case this can only be used for adding new permissions,
|
||||
/// not to remove existing ones.
|
||||
/// The quorum and ackDelay can be changed as well
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum AddMembers { |
||||
V0(AddMembersV0), |
||||
} |
||||
|
||||
/// ObjectRef for EndOfBranch
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum PlainOrEncryptedObjectRef { |
||||
Plain(ObjectRef), |
||||
Encrypted(Vec<u8>), |
||||
} |
||||
|
||||
/// End of branch
|
||||
///
|
||||
/// No more commits accepted afterwards, only acks of this commit
|
||||
/// May reference a fork where the branch continues
|
||||
/// with possibly different members, permissions, validation rules.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct EndOfBranchV0 { |
||||
/// (Encrypted) reference to forked branch (optional)
|
||||
pub fork: Option<PlainOrEncryptedObjectRef>, |
||||
|
||||
/// Expiry time when all commits in the branch should be deleted
|
||||
pub expiry: Timestamp, |
||||
} |
||||
|
||||
/// End of branch
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum EndOfBranch { |
||||
V0(EndOfBranchV0), |
||||
} |
||||
|
||||
/// Transaction with CRDT operations
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Transaction { |
||||
#[serde(with = "serde_bytes")] |
||||
V0(Vec<u8>), |
||||
} |
||||
|
||||
/// Snapshot of a Branch
|
||||
///
|
||||
/// Contains a data structure
|
||||
/// computed from the commits at the specified head.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct SnapshotV0 { |
||||
/// Branch heads the snapshot was made from
|
||||
pub heads: Vec<ObjectId>, |
||||
|
||||
/// Snapshot data structure
|
||||
#[serde(with = "serde_bytes")] |
||||
pub content: Vec<u8>, |
||||
} |
||||
|
||||
/// Snapshot of a Branch
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Snapshot { |
||||
V0(SnapshotV0), |
||||
} |
||||
|
||||
/// Acknowledgement of another Commit
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Ack { |
||||
V0(), |
||||
} |
||||
|
||||
/// Commit body, corresponds to CommitType
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum CommitBody { |
||||
Repository(Repository), |
||||
AddBranch(AddBranch), |
||||
RemoveBranch(RemoveBranch), |
||||
Branch(Branch), |
||||
AddMembers(AddMembers), |
||||
EndOfBranch(EndOfBranch), |
||||
Transaction(Transaction), |
||||
Snapshot(Snapshot), |
||||
Ack(Ack), |
||||
} |
||||
|
||||
/// Content of a Commit
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct CommitContentV0 { |
||||
/// Commit author
|
||||
pub author: PubKey, |
||||
|
||||
/// Author's commit sequence number in this branch
|
||||
pub seq: u32, |
||||
|
||||
/// Branch the commit belongs to
|
||||
pub branch: ObjectRef, |
||||
|
||||
/// Direct dependencies of this commit
|
||||
pub deps: Vec<ObjectRef>, |
||||
|
||||
/// Not directly dependent heads to acknowledge
|
||||
pub acks: Vec<ObjectRef>, |
||||
|
||||
/// Files the commit references
|
||||
pub refs: Vec<ObjectRef>, |
||||
|
||||
/// App-specific metadata (commit message, creation time, etc)
|
||||
#[serde(with = "serde_bytes")] |
||||
pub metadata: Vec<u8>, |
||||
|
||||
/// Object with a CommitBody inside
|
||||
pub body: ObjectRef, |
||||
|
||||
/// Expiry time of the body object
|
||||
pub expiry: Option<Timestamp>, |
||||
} |
||||
|
||||
/// Commit object
|
||||
/// Signed by branch key, or a member key authorized to publish this commit type
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct CommitV0 { |
||||
/// ID of parent Object
|
||||
#[serde(skip)] |
||||
pub id: Option<ObjectId>, |
||||
|
||||
/// Key of parent Object
|
||||
#[serde(skip)] |
||||
pub key: Option<SymKey>, |
||||
|
||||
/// Commit content
|
||||
pub content: CommitContentV0, |
||||
|
||||
/// Signature over the content by the author
|
||||
pub sig: Sig, |
||||
} |
||||
|
||||
/// Commit Object
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum Commit { |
||||
V0(CommitV0), |
||||
} |
||||
|
||||
/// File Object
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub struct FileV0 { |
||||
#[serde(with = "serde_bytes")] |
||||
pub content_type: Vec<u8>, |
||||
|
||||
#[serde(with = "serde_bytes")] |
||||
pub metadata: Vec<u8>, |
||||
|
||||
#[serde(with = "serde_bytes")] |
||||
pub content: Vec<u8>, |
||||
} |
||||
|
||||
/// A file stored in an Object
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum File { |
||||
V0(FileV0), |
||||
} |
||||
|
||||
/// Immutable data stored encrypted in a Merkle tree
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] |
||||
pub enum ObjectContent { |
||||
Commit(Commit), |
||||
CommitBody(CommitBody), |
||||
File(File), |
||||
DepList(DepList), |
||||
} |
@ -0,0 +1,79 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
|
||||
// Copyright 2022 TG x Thoth
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
|
||||
use crate::errors::*; |
||||
use crate::types::*; |
||||
|
||||
use ed25519_dalek::*; |
||||
use rand::rngs::OsRng; |
||||
use std::time::{SystemTime, UNIX_EPOCH}; |
||||
|
||||
pub fn sign( |
||||
author_privkey: PrivKey, |
||||
author_pubkey: PubKey, |
||||
content: &Vec<u8>, |
||||
) -> Result<Sig, NgError> { |
||||
let kp = match (author_privkey, author_pubkey) { |
||||
(PrivKey::Ed25519PrivKey(sk), PubKey::Ed25519PubKey(pk)) => [sk, pk].concat(), |
||||
}; |
||||
let keypair = Keypair::from_bytes(kp.as_slice())?; |
||||
let sig_bytes = keypair.sign(content.as_slice()).to_bytes(); |
||||
let mut it = sig_bytes.chunks_exact(32); |
||||
let mut ss: Ed25519Sig = [[0; 32], [0; 32]]; |
||||
ss[0].copy_from_slice(it.next().unwrap()); |
||||
ss[1].copy_from_slice(it.next().unwrap()); |
||||
Ok(Sig::Ed25519Sig(ss)) |
||||
} |
||||
|
||||
pub fn verify(content: &Vec<u8>, sig: Sig, pub_key: PubKey) -> Result<(), NgError> { |
||||
let pubkey = match pub_key { |
||||
PubKey::Ed25519PubKey(pk) => pk, |
||||
}; |
||||
let pk = PublicKey::from_bytes(&pubkey)?; |
||||
let sig_bytes = match sig { |
||||
Sig::Ed25519Sig(ss) => [ss[0], ss[1]].concat(), |
||||
}; |
||||
let sig = Signature::from_bytes(&sig_bytes)?; |
||||
Ok(pk.verify_strict(content, &sig)?) |
||||
} |
||||
|
||||
pub fn generate_keypair() -> (PrivKey, PubKey) { |
||||
let mut csprng = OsRng {}; |
||||
let keypair: Keypair = Keypair::generate(&mut csprng); |
||||
// println!(
|
||||
// "private key: ({}) {:?}",
|
||||
// keypair.secret.as_bytes().len(),
|
||||
// keypair.secret.as_bytes()
|
||||
// );
|
||||
// println!(
|
||||
// "public key: ({}) {:?}",
|
||||
// keypair.public.as_bytes().len(),
|
||||
// keypair.public.as_bytes()
|
||||
// );
|
||||
let ed_priv_key = keypair.secret.to_bytes(); |
||||
let ed_pub_key = keypair.public.to_bytes(); |
||||
let priv_key = PrivKey::Ed25519PrivKey(ed_priv_key); |
||||
let pub_key = PubKey::Ed25519PubKey(ed_pub_key); |
||||
(priv_key, pub_key) |
||||
} |
||||
|
||||
/// returns the NextGraph Timestamp of now.
|
||||
pub fn now_timestamp() -> Timestamp { |
||||
((SystemTime::now() |
||||
.duration_since(UNIX_EPOCH) |
||||
.unwrap() |
||||
.as_secs() |
||||
- EPOCH_AS_UNIX_TIMESTAMP) |
||||
/ 60) |
||||
.try_into() |
||||
.unwrap() |
||||
} |
@ -0,0 +1,233 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use p2p_repo::broker_store::*; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
|
||||
use debug_print::*; |
||||
use std::path::Path; |
||||
use std::path::PathBuf; |
||||
use std::sync::{Arc, RwLock}; |
||||
|
||||
use rkv::backend::{ |
||||
BackendDatabaseFlags, BackendFlags, BackendIter, BackendWriteFlags, DatabaseFlags, Lmdb, |
||||
LmdbDatabase, LmdbDatabaseFlags, LmdbEnvironment, LmdbRwTransaction, LmdbWriteFlags, |
||||
}; |
||||
use rkv::{ |
||||
Manager, MultiStore, Rkv, SingleStore, StoreError, StoreOptions, Value, WriteFlags, Writer, |
||||
}; |
||||
|
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::error::Error; |
||||
|
||||
pub struct LmdbBrokerStore { |
||||
/// the main store where all the properties of keys are stored
|
||||
main_store: MultiStore<LmdbDatabase>, |
||||
/// the opened environment so we can create new transactions
|
||||
environment: Arc<RwLock<Rkv<LmdbEnvironment>>>, |
||||
/// path for the storage backend data
|
||||
path: String, |
||||
} |
||||
|
||||
impl BrokerStore for LmdbBrokerStore { |
||||
/// Load a single value property from the store.
|
||||
fn get(&self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<Vec<u8>, StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let lock = self.environment.read().unwrap(); |
||||
let reader = lock.read().unwrap(); |
||||
let mut iter = self |
||||
.main_store |
||||
.get(&reader, property) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
match iter.next() { |
||||
Some(Ok(val)) => Ok(val.1.to_bytes().unwrap()), |
||||
Some(Err(_e)) => Err(StorageError::BackendError), |
||||
None => Err(StorageError::NotFound), |
||||
} |
||||
} |
||||
|
||||
/// Load all the values of a property from the store.
|
||||
fn get_all( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
) -> Result<Vec<Vec<u8>>, StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let lock = self.environment.read().unwrap(); |
||||
let reader = lock.read().unwrap(); |
||||
let mut iter = self |
||||
.main_store |
||||
.get(&reader, property) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
let mut vector: Vec<Vec<u8>> = vec![]; |
||||
while let res = iter.next() { |
||||
vector.push(match res { |
||||
Some(Ok(val)) => val.1.to_bytes().unwrap(), |
||||
Some(Err(_e)) => return Err(StorageError::BackendError), |
||||
None => { |
||||
break; |
||||
} |
||||
}); |
||||
} |
||||
Ok(vector) |
||||
} |
||||
|
||||
/// Check if a specific value exists for a property from the store.
|
||||
fn has_property_value( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let lock = self.environment.read().unwrap(); |
||||
let reader = lock.read().unwrap(); |
||||
let exists = self |
||||
.main_store |
||||
.get_key_value(&reader, property, &Value::Blob(value.as_slice())) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
if exists { |
||||
Ok(()) |
||||
} else { |
||||
Err(StorageError::NotFound) |
||||
} |
||||
} |
||||
|
||||
/// Save a property value to the store.
|
||||
fn put( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
self.main_store |
||||
.put(&mut writer, property, &Value::Blob(value.as_slice())) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
|
||||
writer.commit().unwrap(); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// Replace the property of a key (single value) to the store.
|
||||
fn replace( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
self.main_store |
||||
.delete_all(&mut writer, property.clone()) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
|
||||
self.main_store |
||||
.put(&mut writer, property, &Value::Blob(value.as_slice())) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
|
||||
writer.commit().unwrap(); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// Delete a property from the store.
|
||||
fn del(&self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<(), StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
self.main_store |
||||
.delete_all(&mut writer, property) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
|
||||
writer.commit().unwrap(); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// Delete a specific value for a property from the store.
|
||||
fn del_property_value( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
self.main_store |
||||
.delete(&mut writer, property, &Value::Blob(value.as_slice())) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
|
||||
writer.commit().unwrap(); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// Delete all properties of a key from the store.
|
||||
fn del_all(&self, prefix: u8, key: &Vec<u8>, all_suffixes: &[u8]) -> Result<(), StorageError> { |
||||
for suffix in all_suffixes { |
||||
self.del(prefix, key, Some(*suffix))?; |
||||
} |
||||
if all_suffixes.is_empty() { |
||||
self.del(prefix, key, None)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl LmdbBrokerStore { |
||||
pub fn path(&self) -> PathBuf { |
||||
PathBuf::from(&self.path) |
||||
} |
||||
|
||||
fn compute_property(prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Vec<u8> { |
||||
let mut new: Vec<u8> = Vec::with_capacity(key.len() + 2); |
||||
new.push(prefix); |
||||
new.extend(key); |
||||
if suffix.is_some() { |
||||
new.push(suffix.unwrap()) |
||||
} |
||||
new |
||||
} |
||||
|
||||
/// Opens the store and returns a BrokerStore object that should be kept and used to manipulate Accounts, Overlays, Topics and options
|
||||
/// The key is the encryption key for the data at rest.
|
||||
pub fn open<'a>(path: &Path, key: [u8; 32]) -> LmdbBrokerStore { |
||||
let mut manager = Manager::<LmdbEnvironment>::singleton().write().unwrap(); |
||||
let shared_rkv = manager |
||||
.get_or_create(path, |path| { |
||||
//Rkv::new::<Lmdb>(path) // use this instead to disable encryption
|
||||
Rkv::with_encryption_key_and_mapsize::<Lmdb>(path, key, 2 * 1024 * 1024 * 1024) |
||||
}) |
||||
.unwrap(); |
||||
let env = shared_rkv.read().unwrap(); |
||||
|
||||
println!("created env with LMDB Version: {}", env.version()); |
||||
|
||||
let main_store = env.open_multi("main", StoreOptions::create()).unwrap(); |
||||
|
||||
LmdbBrokerStore { |
||||
environment: shared_rkv.clone(), |
||||
main_store, |
||||
path: path.to_str().unwrap().to_string(), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
pub mod repo_store; |
||||
|
||||
pub mod broker_store; |
@ -0,0 +1,997 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
|
||||
use debug_print::*; |
||||
use std::path::Path; |
||||
use std::sync::{Arc, RwLock}; |
||||
|
||||
use rkv::backend::{ |
||||
BackendDatabaseFlags, BackendFlags, BackendIter, BackendWriteFlags, DatabaseFlags, Lmdb, |
||||
LmdbDatabase, LmdbDatabaseFlags, LmdbEnvironment, LmdbRwTransaction, LmdbWriteFlags, |
||||
}; |
||||
use rkv::{ |
||||
Manager, MultiIntegerStore, Rkv, SingleStore, StoreError, StoreOptions, Value, WriteFlags, |
||||
Writer, |
||||
}; |
||||
|
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::error::Error; |
||||
|
||||
pub struct LmdbRepoStore { |
||||
/// the main store where all the repo blocks are stored
|
||||
main_store: SingleStore<LmdbDatabase>, |
||||
/// store for the pin boolean, recently_used timestamp, and synced boolean
|
||||
meta_store: SingleStore<LmdbDatabase>, |
||||
/// store for the expiry timestamp
|
||||
expiry_store: MultiIntegerStore<LmdbDatabase, u32>, |
||||
/// store for the LRU list
|
||||
recently_used_store: MultiIntegerStore<LmdbDatabase, u32>, |
||||
/// the opened environment so we can create new transactions
|
||||
environment: Arc<RwLock<Rkv<LmdbEnvironment>>>, |
||||
} |
||||
|
||||
// TODO: versioning V0
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |
||||
struct BlockMeta { |
||||
pub pin: bool, |
||||
pub last_used: Timestamp, |
||||
pub synced: bool, |
||||
} |
||||
|
||||
impl RepoStore for LmdbRepoStore { |
||||
/// Retrieves a block from the storage backend.
|
||||
fn get(&self, block_id: &BlockId) -> Result<Block, StorageError> { |
||||
let lock = self.environment.read().unwrap(); |
||||
let reader = lock.read().unwrap(); |
||||
let block_id_ser = serde_bare::to_vec(&block_id).unwrap(); |
||||
let block_ser_res = self.main_store.get(&reader, block_id_ser.clone()); |
||||
match block_ser_res { |
||||
Err(e) => Err(StorageError::BackendError), |
||||
Ok(None) => Err(StorageError::NotFound), |
||||
Ok(Some(block_ser)) => { |
||||
// updating recently_used
|
||||
// first getting the meta for this BlockId
|
||||
let meta_ser = self.meta_store.get(&reader, block_id_ser.clone()).unwrap(); |
||||
match meta_ser { |
||||
Some(meta_value) => { |
||||
let mut meta = |
||||
serde_bare::from_slice::<BlockMeta>(&meta_value.to_bytes().unwrap()) |
||||
.unwrap(); |
||||
if meta.synced { |
||||
let mut writer = lock.write().unwrap(); |
||||
let now = now_timestamp(); |
||||
if !meta.pin { |
||||
// we remove the previous timestamp (last_used) from recently_used_store
|
||||
self.remove_from_lru(&mut writer, &block_id_ser, &meta.last_used) |
||||
.unwrap(); |
||||
// we add an entry to recently_used_store with now
|
||||
self.add_to_lru(&mut writer, &block_id_ser, &now).unwrap(); |
||||
} |
||||
// we save the new meta (with last_used:now)
|
||||
meta.last_used = now; |
||||
let new_meta_ser = serde_bare::to_vec(&meta).unwrap(); |
||||
self.meta_store |
||||
.put( |
||||
&mut writer, |
||||
block_id_ser, |
||||
&Value::Blob(new_meta_ser.as_slice()), |
||||
) |
||||
.unwrap(); |
||||
// commit
|
||||
writer.commit().unwrap(); |
||||
} |
||||
} |
||||
_ => {} // there is no meta. we do nothing since we start to record LRU only once synced == true.
|
||||
} |
||||
|
||||
match serde_bare::from_slice::<Block>(&block_ser.to_bytes().unwrap()) { |
||||
Err(_e) => Err(StorageError::InvalidValue), |
||||
Ok(o) => { |
||||
if o.id() != *block_id { |
||||
debug_println!( |
||||
"Invalid ObjectId.\nExp: {:?}\nGot: {:?}\nContent: {:?}", |
||||
block_id, |
||||
o.id(), |
||||
o |
||||
); |
||||
panic!("CORRUPTION OF DATA !"); |
||||
} |
||||
Ok(o) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Adds a block in the storage backend.
|
||||
/// The block is persisted to disk.
|
||||
/// Returns the BlockId of the Block.
|
||||
fn put(&self, block: &Block) -> Result<BlockId, StorageError> { |
||||
let block_ser = serde_bare::to_vec(&block).unwrap(); |
||||
|
||||
let block_id = block.id(); |
||||
let block_id_ser = serde_bare::to_vec(&block_id).unwrap(); |
||||
|
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
|
||||
// TODO: check if the block is already in store? if yes, don't put it again.
|
||||
// I didnt do it yet because it is extra cost. surely a get on the store is lighter than a put
|
||||
// but doing a get in additing to a put for every call, is probably even costlier. better to deal with that at the higher level
|
||||
|
||||
self.main_store |
||||
.put( |
||||
&mut writer, |
||||
&block_id_ser, |
||||
&Value::Blob(block_ser.as_slice()), |
||||
) |
||||
.unwrap(); |
||||
|
||||
// if it has an expiry, adding the BlockId to the expiry_store
|
||||
match block.expiry() { |
||||
Some(expiry) => { |
||||
self.expiry_store |
||||
.put(&mut writer, expiry, &Value::Blob(block_id_ser.as_slice())) |
||||
.unwrap(); |
||||
} |
||||
_ => {} |
||||
} |
||||
writer.commit().unwrap(); |
||||
|
||||
Ok(block_id) |
||||
} |
||||
|
||||
/// Removes the block from the storage backend.
|
||||
/// The removed block is returned, so it can be inspected.
|
||||
/// Also returned is the approximate size of of free space that was reclaimed.
|
||||
fn del(&self, block_id: &BlockId) -> Result<(Block, usize), StorageError> { |
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
let block_id_ser = serde_bare::to_vec(&block_id).unwrap(); |
||||
// retrieving the block itself (we need the expiry)
|
||||
let block_ser = self |
||||
.main_store |
||||
.get(&writer, block_id_ser.clone()) |
||||
.unwrap() |
||||
.ok_or(StorageError::NotFound)?; |
||||
let slice = block_ser.to_bytes().unwrap(); |
||||
let block = serde_bare::from_slice::<Block>(&slice).unwrap(); //FIXME propagate error?
|
||||
let meta_res = self.meta_store.get(&writer, block_id_ser.clone()).unwrap(); |
||||
if meta_res.is_some() { |
||||
let meta = serde_bare::from_slice::<BlockMeta>(&meta_res.unwrap().to_bytes().unwrap()) |
||||
.unwrap(); |
||||
if meta.last_used != 0 { |
||||
self.remove_from_lru(&mut writer, &block_id_ser.clone(), &meta.last_used) |
||||
.unwrap(); |
||||
} |
||||
// removing the meta
|
||||
self.meta_store |
||||
.delete(&mut writer, block_id_ser.clone()) |
||||
.unwrap(); |
||||
} |
||||
// delete block from main_store
|
||||
self.main_store |
||||
.delete(&mut writer, block_id_ser.clone()) |
||||
.unwrap(); |
||||
// remove BlockId from expiry_store, if any expiry
|
||||
match block.expiry() { |
||||
Some(expiry) => { |
||||
self.expiry_store |
||||
.delete( |
||||
&mut writer, |
||||
expiry, |
||||
&Value::Blob(block_id_ser.clone().as_slice()), |
||||
) |
||||
.unwrap(); |
||||
} |
||||
_ => {} |
||||
} |
||||
|
||||
writer.commit().unwrap(); |
||||
Ok((block, slice.len())) |
||||
} |
||||
|
||||
} |
||||
|
||||
impl LmdbRepoStore { |
||||
/// Opens the store and returns a RepoStore object that should be kept and used to call put/get/delete/pin
|
||||
/// The key is the encryption key for the data at rest.
|
||||
pub fn open<'a>(path: &Path, key: [u8; 32]) -> LmdbRepoStore { |
||||
let mut manager = Manager::<LmdbEnvironment>::singleton().write().unwrap(); |
||||
let shared_rkv = manager |
||||
.get_or_create(path, |path| { |
||||
//Rkv::new::<Lmdb>(path) // use this instead to disable encryption
|
||||
Rkv::with_encryption_key_and_mapsize::<Lmdb>(path, key, 2 * 1024 * 1024 * 1024) |
||||
}) |
||||
.unwrap(); |
||||
let env = shared_rkv.read().unwrap(); |
||||
|
||||
println!( |
||||
"created env with LMDB Version: {} key: {}", |
||||
env.version(), |
||||
hex::encode(&key) |
||||
); |
||||
|
||||
let main_store = env.open_single("main", StoreOptions::create()).unwrap(); |
||||
let meta_store = env.open_single("meta", StoreOptions::create()).unwrap(); |
||||
let mut opts = StoreOptions::<LmdbDatabaseFlags>::create(); |
||||
opts.flags.set(DatabaseFlags::DUP_FIXED, true); |
||||
let expiry_store = env.open_multi_integer("expiry", opts).unwrap(); |
||||
let recently_used_store = env.open_multi_integer("recently_used", opts).unwrap(); |
||||
|
||||
LmdbRepoStore { |
||||
environment: shared_rkv.clone(), |
||||
main_store, |
||||
meta_store, |
||||
expiry_store, |
||||
recently_used_store, |
||||
} |
||||
} |
||||
|
||||
//FIXME: use BlockId, not ObjectId. this is a block level operation
|
||||
/// Pins the object
|
||||
pub fn pin(&self, object_id: &ObjectId) -> Result<(), StorageError> { |
||||
self.set_pin(object_id, true) |
||||
} |
||||
|
||||
//FIXME: use BlockId, not ObjectId. this is a block level operation
|
||||
/// Unpins the object
|
||||
pub fn unpin(&self, object_id: &ObjectId) -> Result<(), StorageError> { |
||||
self.set_pin(object_id, false) |
||||
} |
||||
|
||||
//FIXME: use BlockId, not ObjectId. this is a block level operation
|
||||
/// Sets the pin for that Object. if add is true, will add the pin. if false, will remove the pin.
|
||||
/// A pin on an object prevents it from being removed when the store is making some disk space by using the LRU.
|
||||
/// A pin does not override the expiry. If expiry is set and is reached, the obejct will be deleted, no matter what.
|
||||
pub fn set_pin(&self, object_id: &ObjectId, add: bool) -> Result<(), StorageError> { |
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
let obj_id_ser = serde_bare::to_vec(&object_id).unwrap(); |
||||
let meta_ser = self.meta_store.get(&writer, &obj_id_ser).unwrap(); |
||||
let mut meta; |
||||
|
||||
// if adding a pin, if there is a meta (if already pinned, return) and is synced, remove the last_used timestamp from recently_used_store
|
||||
// if no meta, create it with pin:true, synced: false
|
||||
// if removing a pin (if pin already removed, return), if synced, add an entry to recently_used_store with the last_used timestamp (as found in meta, dont use now)
|
||||
|
||||
match meta_ser { |
||||
Some(meta_value) => { |
||||
meta = |
||||
serde_bare::from_slice::<BlockMeta>(&meta_value.to_bytes().unwrap()).unwrap(); |
||||
|
||||
if add == meta.pin { |
||||
// pinning while already pinned, or unpinning while already unpinned. NOP
|
||||
return Ok(()); |
||||
}; |
||||
|
||||
meta.pin = add; |
||||
|
||||
if meta.synced { |
||||
if add { |
||||
// we remove the previous timestamp (last_used) from recently_used_store
|
||||
self.remove_from_lru(&mut writer, &obj_id_ser, &meta.last_used) |
||||
.unwrap(); |
||||
} else { |
||||
// we add an entry to recently_used_store with last_used
|
||||
self.add_to_lru(&mut writer, &obj_id_ser, &meta.last_used) |
||||
.unwrap(); |
||||
} |
||||
} |
||||
} |
||||
None => { |
||||
if add { |
||||
meta = BlockMeta { |
||||
pin: true, |
||||
synced: false, |
||||
last_used: 0, |
||||
} |
||||
} else { |
||||
// there is no meta, and user wants to unpin, so let's leave everything as it is.
|
||||
return Ok(()); |
||||
} |
||||
} |
||||
} |
||||
let new_meta_ser = serde_bare::to_vec(&meta).unwrap(); |
||||
self.meta_store |
||||
.put( |
||||
&mut writer, |
||||
obj_id_ser, |
||||
&Value::Blob(new_meta_ser.as_slice()), |
||||
) |
||||
.unwrap(); |
||||
// commit
|
||||
writer.commit().unwrap(); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
//FIXME: use BlockId, not ObjectId. this is a block level operation
|
||||
/// the broker calls this method when the block has been retrieved/synced by enough peers and it
|
||||
/// can now be included in the LRU for potential garbage collection.
|
||||
/// If this method has not been called on a block, it will be kept in the store and will not enter LRU.
|
||||
pub fn has_been_synced(&self, block_id: &BlockId, when: Option<u32>) -> Result<(), Error> { |
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
let block_id_ser = serde_bare::to_vec(&block_id).unwrap(); |
||||
let meta_ser = self.meta_store.get(&writer, block_id_ser.clone()).unwrap(); |
||||
let mut meta; |
||||
let now = match when { |
||||
None => now_timestamp(), |
||||
Some(w) => w, |
||||
}; |
||||
// get the meta. if no meta, it is ok, we will create it after (with pin:false and synced:true)
|
||||
// if already synced, return
|
||||
// update the meta with last_used:now and synced:true
|
||||
// if pinned, save and return
|
||||
// otherwise add an entry to recently_used_store with now
|
||||
|
||||
match meta_ser { |
||||
Some(meta_value) => { |
||||
meta = |
||||
serde_bare::from_slice::<BlockMeta>(&meta_value.to_bytes().unwrap()).unwrap(); |
||||
|
||||
if meta.synced { |
||||
// already synced. NOP
|
||||
return Ok(()); |
||||
}; |
||||
|
||||
meta.synced = true; |
||||
meta.last_used = now; |
||||
|
||||
if !meta.pin { |
||||
// we add an entry to recently_used_store with now
|
||||
println!("adding to LRU"); |
||||
self.add_to_lru(&mut writer, &block_id_ser, &now).unwrap(); |
||||
} |
||||
} |
||||
None => { |
||||
meta = BlockMeta { |
||||
pin: false, |
||||
synced: true, |
||||
last_used: now, |
||||
}; |
||||
println!("adding to LRU also"); |
||||
self.add_to_lru(&mut writer, &block_id_ser, &now).unwrap(); |
||||
} |
||||
} |
||||
let new_meta_ser = serde_bare::to_vec(&meta).unwrap(); |
||||
self.meta_store |
||||
.put( |
||||
&mut writer, |
||||
block_id_ser, |
||||
&Value::Blob(new_meta_ser.as_slice()), |
||||
) |
||||
.unwrap(); |
||||
// commit
|
||||
writer.commit().unwrap(); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// Removes all the blocks that have expired.
|
||||
/// The broker should call this method periodically.
|
||||
pub fn remove_expired(&self) -> Result<(), Error> { |
||||
let mut block_ids: Vec<BlockId> = vec![]; |
||||
|
||||
{ |
||||
let lock = self.environment.read().unwrap(); |
||||
let reader = lock.read().unwrap(); |
||||
|
||||
let mut iter = self |
||||
.expiry_store |
||||
.iter_prev_dup_from(&reader, now_timestamp()) |
||||
.unwrap(); |
||||
|
||||
while let Some(Ok(mut sub_iter)) = iter.next() { |
||||
while let Some(Ok(k)) = sub_iter.next() { |
||||
//println!("removing {:?} {:?}", k.0, k.1);
|
||||
let block_id = serde_bare::from_slice::<ObjectId>(k.1).unwrap(); |
||||
block_ids.push(block_id); |
||||
} |
||||
} |
||||
} |
||||
for block_id in block_ids { |
||||
self.del(&block_id).unwrap(); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Removes some blocks that haven't been used for a while, reclaiming some space on disk.
|
||||
/// The oldest are removed first, until the total amount of data removed is at least equal to size,
|
||||
/// or the LRU list became empty. The approximate size of the storage space that was reclaimed is returned.
|
||||
pub fn remove_least_used(&self, size: usize) -> usize { |
||||
let mut block_ids: Vec<BlockId> = vec![]; |
||||
let mut total: usize = 0; |
||||
|
||||
{ |
||||
let lock = self.environment.read().unwrap(); |
||||
let reader = lock.read().unwrap(); |
||||
|
||||
let mut iter = self.recently_used_store.iter_start(&reader).unwrap(); |
||||
|
||||
while let Some(Ok(entry)) = iter.next() { |
||||
let block_id = |
||||
serde_bare::from_slice::<ObjectId>(entry.1.to_bytes().unwrap().as_slice()) |
||||
.unwrap(); |
||||
block_ids.push(block_id); |
||||
} |
||||
} |
||||
for block_id in block_ids { |
||||
let (block, block_size) = self.del(&block_id).unwrap(); |
||||
println!("removed {:?}", block_id); |
||||
total += block_size; |
||||
if total >= size { |
||||
break; |
||||
} |
||||
} |
||||
total |
||||
} |
||||
|
||||
fn remove_from_lru( |
||||
&self, |
||||
writer: &mut Writer<LmdbRwTransaction>, |
||||
block_id_ser: &Vec<u8>, |
||||
time: &Timestamp, |
||||
) -> Result<(), StoreError> { |
||||
self.recently_used_store |
||||
.delete(writer, *time, &Value::Blob(block_id_ser.as_slice())) |
||||
} |
||||
|
||||
fn add_to_lru( |
||||
&self, |
||||
writer: &mut Writer<LmdbRwTransaction>, |
||||
block_id_ser: &Vec<u8>, |
||||
time: &Timestamp, |
||||
) -> Result<(), StoreError> { |
||||
let mut flag = LmdbWriteFlags::empty(); |
||||
flag.set(WriteFlags::APPEND_DUP, true); |
||||
self.recently_used_store.put_with_flags( |
||||
writer, |
||||
*time, |
||||
&Value::Blob(block_id_ser.as_slice()), |
||||
flag, |
||||
) |
||||
} |
||||
|
||||
fn list_all(&self) { |
||||
let lock = self.environment.read().unwrap(); |
||||
let reader = lock.read().unwrap(); |
||||
println!("MAIN"); |
||||
let mut iter = self.main_store.iter_start(&reader).unwrap(); |
||||
while let Some(Ok(entry)) = iter.next() { |
||||
println!("{:?} {:?}", entry.0, entry.1) |
||||
} |
||||
println!("META"); |
||||
let mut iter2 = self.meta_store.iter_start(&reader).unwrap(); |
||||
while let Some(Ok(entry)) = iter2.next() { |
||||
println!("{:?} {:?}", entry.0, entry.1) |
||||
} |
||||
println!("EXPIRY"); |
||||
let mut iter3 = self.expiry_store.iter_start(&reader).unwrap(); |
||||
while let Some(Ok(entry)) = iter3.next() { |
||||
println!("{:?} {:?}", entry.0, entry.1) |
||||
} |
||||
println!("LRU"); |
||||
let mut iter4 = self.recently_used_store.iter_start(&reader).unwrap(); |
||||
while let Some(Ok(entry)) = iter4.next() { |
||||
println!("{:?} {:?}", entry.0, entry.1) |
||||
} |
||||
} |
||||
} |
||||
#[cfg(test)] |
||||
mod test { |
||||
|
||||
use crate::repo_store::LmdbRepoStore; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
use rkv::backend::{BackendInfo, BackendStat, Lmdb, LmdbEnvironment}; |
||||
use rkv::{Manager, Rkv, StoreOptions, Value}; |
||||
#[allow(unused_imports)] |
||||
use std::time::Duration; |
||||
#[allow(unused_imports)] |
||||
use std::{fs, thread}; |
||||
use tempfile::Builder; |
||||
|
||||
#[test] |
||||
pub fn test_remove_least_used() { |
||||
let path_str = "test-env"; |
||||
let root = Builder::new().prefix(path_str).tempdir().unwrap(); |
||||
let key: [u8; 32] = [0; 32]; |
||||
fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let mut store = LmdbRepoStore::open(root.path(), key); |
||||
let mut now = now_timestamp(); |
||||
now -= 200; |
||||
// TODO: fix the LMDB bug that is triggered with x max set to 86 !!!
|
||||
for x in 1..85 { |
||||
let block = Block::new( |
||||
Vec::new(), |
||||
ObjectDeps::ObjectIdList(Vec::new()), |
||||
None, |
||||
vec![x; 10], |
||||
None, |
||||
); |
||||
let block_id = store.put(&block).unwrap(); |
||||
println!("#{} -> objId {:?}", x, block_id); |
||||
store |
||||
.has_been_synced(&block_id, Some(now + x as u32)) |
||||
.unwrap(); |
||||
} |
||||
|
||||
let ret = store.remove_least_used(200); |
||||
println!("removed {}", ret); |
||||
assert_eq!(ret, 208) |
||||
|
||||
//store.list_all();
|
||||
} |
||||
|
||||
#[test] |
||||
pub fn test_set_pin() { |
||||
let path_str = "test-env"; |
||||
let root = Builder::new().prefix(path_str).tempdir().unwrap(); |
||||
let key: [u8; 32] = [0; 32]; |
||||
fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let mut store = LmdbRepoStore::open(root.path(), key); |
||||
let mut now = now_timestamp(); |
||||
now -= 200; |
||||
// TODO: fix the LMDB bug that is triggered with x max set to 86 !!!
|
||||
for x in 1..100 { |
||||
let block = Block::new( |
||||
Vec::new(), |
||||
ObjectDeps::ObjectIdList(Vec::new()), |
||||
None, |
||||
vec![x; 10], |
||||
None, |
||||
); |
||||
let obj_id = store.put(&block).unwrap(); |
||||
println!("#{} -> objId {:?}", x, obj_id); |
||||
store.set_pin(&obj_id, true).unwrap(); |
||||
store |
||||
.has_been_synced(&obj_id, Some(now + x as u32)) |
||||
.unwrap(); |
||||
} |
||||
|
||||
let ret = store.remove_least_used(200); |
||||
println!("removed {}", ret); |
||||
assert_eq!(ret, 0); |
||||
|
||||
store.list_all(); |
||||
} |
||||
|
||||
#[test] |
||||
pub fn test_get_valid_value_size() { |
||||
assert_eq!(store_valid_value_size(0), 4072); |
||||
assert_eq!(store_valid_value_size(2), 4072); |
||||
assert_eq!(store_valid_value_size(4072), 4072); |
||||
assert_eq!(store_valid_value_size(4072 + 1), 4072 + 4096); |
||||
assert_eq!(store_valid_value_size(4072 + 4096), 4072 + 4096); |
||||
assert_eq!(store_valid_value_size(4072 + 4096 + 1), 4072 + 4096 + 4096); |
||||
assert_eq!( |
||||
store_valid_value_size(4072 + 4096 + 4096), |
||||
4072 + 4096 + 4096 |
||||
); |
||||
assert_eq!( |
||||
store_valid_value_size(4072 + 4096 + 4096 + 1), |
||||
4072 + 4096 + 4096 + 4096 |
||||
); |
||||
assert_eq!(store_valid_value_size(4072 + 4096 * 511), 4072 + 4096 * 511); |
||||
assert_eq!( |
||||
store_valid_value_size(4072 + 4096 * 511 + 1), |
||||
4072 + 4096 * 511 |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
pub fn test_remove_expired() { |
||||
let path_str = "test-env"; |
||||
let root = Builder::new().prefix(path_str).tempdir().unwrap(); |
||||
let key: [u8; 32] = [0; 32]; |
||||
fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let mut store = LmdbRepoStore::open(root.path(), key); |
||||
|
||||
let now = now_timestamp(); |
||||
let list = [ |
||||
now - 10, |
||||
now - 6, |
||||
now - 6, |
||||
now - 3, |
||||
now - 2, |
||||
now - 1, //#5 should be removed, and above
|
||||
now + 3, |
||||
now + 4, |
||||
now + 4, |
||||
now + 5, |
||||
now + 10, |
||||
]; |
||||
let mut block_ids: Vec<ObjectId> = Vec::with_capacity(11); |
||||
println!("now {}", now); |
||||
|
||||
let mut i = 0u8; |
||||
for expiry in list { |
||||
//let i: u8 = (expiry + 10 - now).try_into().unwrap();
|
||||
let block = Block::new( |
||||
Vec::new(), |
||||
ObjectDeps::ObjectIdList(Vec::new()), |
||||
Some(expiry), |
||||
[i].to_vec(), |
||||
None, |
||||
); |
||||
let block_id = store.put(&block).unwrap(); |
||||
println!("#{} -> objId {:?}", i, block_id); |
||||
block_ids.push(block_id); |
||||
i += 1; |
||||
} |
||||
|
||||
store.remove_expired().unwrap(); |
||||
|
||||
assert!(store.get(block_ids.get(0).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(1).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(2).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(5).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(6).unwrap()).is_ok()); |
||||
assert!(store.get(block_ids.get(7).unwrap()).is_ok()); |
||||
|
||||
//store.list_all();
|
||||
} |
||||
|
||||
#[test] |
||||
pub fn test_remove_all_expired() { |
||||
let path_str = "test-env"; |
||||
let root = Builder::new().prefix(path_str).tempdir().unwrap(); |
||||
let key: [u8; 32] = [0; 32]; |
||||
fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let mut store = LmdbRepoStore::open(root.path(), key); |
||||
|
||||
let now = now_timestamp(); |
||||
let list = [ |
||||
now - 10, |
||||
now - 6, |
||||
now - 6, |
||||
now - 3, |
||||
now - 2, |
||||
now - 2, //#5 should be removed, and above
|
||||
]; |
||||
let mut block_ids: Vec<ObjectId> = Vec::with_capacity(6); |
||||
println!("now {}", now); |
||||
|
||||
let mut i = 0u8; |
||||
for expiry in list { |
||||
//let i: u8 = (expiry + 10 - now).try_into().unwrap();
|
||||
let block = Block::new( |
||||
Vec::new(), |
||||
ObjectDeps::ObjectIdList(Vec::new()), |
||||
Some(expiry), |
||||
[i].to_vec(), |
||||
None, |
||||
); |
||||
let block_id = store.put(&block).unwrap(); |
||||
println!("#{} -> objId {:?}", i, block_id); |
||||
block_ids.push(block_id); |
||||
i += 1; |
||||
} |
||||
|
||||
store.remove_expired().unwrap(); |
||||
|
||||
assert!(store.get(block_ids.get(0).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(1).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(2).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(3).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(4).unwrap()).is_err()); |
||||
assert!(store.get(block_ids.get(5).unwrap()).is_err()); |
||||
} |
||||
|
||||
#[test] |
||||
pub fn test_remove_empty_expired() { |
||||
let path_str = "test-env"; |
||||
let root = Builder::new().prefix(path_str).tempdir().unwrap(); |
||||
let key: [u8; 32] = [0; 32]; |
||||
fs::create_dir_all(root.path()).unwrap(); |
||||
println!("{}", root.path().to_str().unwrap()); |
||||
let store = LmdbRepoStore::open(root.path(), key); |
||||
store.remove_expired().unwrap(); |
||||
} |
||||
|
||||
#[test] |
||||
pub fn test_store_block() { |
||||
let path_str = "test-env"; |
||||
let root = Builder::new().prefix(path_str).tempdir().unwrap(); |
||||
|
||||
let key: [u8; 32] = [0; 32]; |
||||
fs::create_dir_all(root.path()).unwrap(); |
||||
|
||||
println!("{}", root.path().to_str().unwrap()); |
||||
|
||||
let mut store = LmdbRepoStore::open(root.path(), key); |
||||
|
||||
let block = Block::new( |
||||
Vec::new(), |
||||
ObjectDeps::ObjectIdList(Vec::new()), |
||||
None, |
||||
b"abc".to_vec(), |
||||
None, |
||||
); |
||||
|
||||
let block_id = store.put(&block).unwrap(); |
||||
assert_eq!(block_id, block.id()); |
||||
|
||||
println!("ObjectId: {:?}", block_id); |
||||
assert_eq!( |
||||
block_id, |
||||
Digest::Blake3Digest32([ |
||||
155, 83, 186, 17, 95, 10, 80, 31, 111, 24, 250, 64, 8, 145, 71, 193, 103, 246, 202, |
||||
28, 202, 144, 63, 65, 85, 229, 136, 85, 202, 34, 13, 85 |
||||
]) |
||||
); |
||||
|
||||
let block_res = store.get(&block_id).unwrap(); |
||||
|
||||
println!("Block: {:?}", block_res); |
||||
assert_eq!(block_res.id(), block.id()); |
||||
} |
||||
|
||||
#[test] |
||||
pub fn test_lmdb() { |
||||
let path_str = "test-env"; |
||||
let root = Builder::new().prefix(path_str).tempdir().unwrap(); |
||||
|
||||
// we set an encryption key with all zeros... for test purpose only ;)
|
||||
let key: [u8; 32] = [0; 32]; |
||||
{ |
||||
fs::create_dir_all(root.path()).unwrap(); |
||||
|
||||
println!("{}", root.path().to_str().unwrap()); |
||||
|
||||
let mut manager = Manager::<LmdbEnvironment>::singleton().write().unwrap(); |
||||
let shared_rkv = manager |
||||
.get_or_create(root.path(), |path| { |
||||
// Rkv::new::<Lmdb>(path) // use this instead to disable encryption
|
||||
Rkv::with_encryption_key_and_mapsize::<Lmdb>(path, key, 2 * 1024 * 1024 * 1024) |
||||
}) |
||||
.unwrap(); |
||||
let env = shared_rkv.read().unwrap(); |
||||
|
||||
println!("LMDB Version: {}", env.version()); |
||||
|
||||
let store = env.open_single("testdb", StoreOptions::create()).unwrap(); |
||||
|
||||
{ |
||||
// Use a write transaction to mutate the store via a `Writer`. There can be only
|
||||
// one writer for a given environment, so opening a second one will block until
|
||||
// the first completes.
|
||||
let mut writer = env.write().unwrap(); |
||||
|
||||
// Keys are `AsRef<[u8]>`, while values are `Value` enum instances. Use the `Blob`
|
||||
// variant to store arbitrary collections of bytes. Putting data returns a
|
||||
// `Result<(), StoreError>`, where StoreError is an enum identifying the reason
|
||||
// for a failure.
|
||||
// store.put(&mut writer, "int", &Value::I64(1234)).unwrap();
|
||||
// store
|
||||
// .put(&mut writer, "uint", &Value::U64(1234_u64))
|
||||
// .unwrap();
|
||||
// store
|
||||
// .put(&mut writer, "float", &Value::F64(1234.0.into()))
|
||||
// .unwrap();
|
||||
// store
|
||||
// .put(&mut writer, "instant", &Value::Instant(1528318073700))
|
||||
// .unwrap();
|
||||
// store
|
||||
// .put(&mut writer, "boolean", &Value::Bool(true))
|
||||
// .unwrap();
|
||||
// store
|
||||
// .put(&mut writer, "string", &Value::Str("Héllo, wörld!"))
|
||||
// .unwrap();
|
||||
// store
|
||||
// .put(
|
||||
// &mut writer,
|
||||
// "json",
|
||||
// &Value::Json(r#"{"foo":"bar", "number": 1}"#),
|
||||
// )
|
||||
// .unwrap();
|
||||
const EXTRA: usize = 2095; // + 4096 * 524280 + 0;
|
||||
let key: [u8; 33] = [0; 33]; |
||||
let key2: [u8; 33] = [2; 33]; |
||||
let key3: [u8; 33] = [3; 33]; |
||||
let key4: [u8; 33] = [4; 33]; |
||||
//let value: [u8; 1977 + EXTRA] = [1; 1977 + EXTRA];
|
||||
let value = vec![1; 1977 + EXTRA]; |
||||
let value2: [u8; 1977 + 1] = [1; 1977 + 1]; |
||||
let value4: [u8; 953 + 0] = [1; 953 + 0]; |
||||
store.put(&mut writer, key, &Value::Blob(&value2)).unwrap(); |
||||
store.put(&mut writer, key2, &Value::Blob(&value2)).unwrap(); |
||||
// store.put(&mut writer, key3, &Value::Blob(&value)).unwrap();
|
||||
// store.put(&mut writer, key4, &Value::Blob(&value4)).unwrap();
|
||||
|
||||
// You must commit a write transaction before the writer goes out of scope, or the
|
||||
// transaction will abort and the data won't persist.
|
||||
writer.commit().unwrap(); |
||||
let reader = env.read().expect("reader"); |
||||
let stat = store.stat(&reader).unwrap(); |
||||
|
||||
println!("LMDB stat page_size : {}", stat.page_size()); |
||||
println!("LMDB stat depth : {}", stat.depth()); |
||||
println!("LMDB stat branch_pages : {}", stat.branch_pages()); |
||||
println!("LMDB stat leaf_pages : {}", stat.leaf_pages()); |
||||
println!("LMDB stat overflow_pages : {}", stat.overflow_pages()); |
||||
println!("LMDB stat entries : {}", stat.entries()); |
||||
} |
||||
|
||||
// {
|
||||
// // Use a read transaction to query the store via a `Reader`. There can be multiple
|
||||
// // concurrent readers for a store, and readers never block on a writer nor other
|
||||
// // readers.
|
||||
// let reader = env.read().expect("reader");
|
||||
|
||||
// // Keys are `AsRef<u8>`, and the return value is `Result<Option<Value>, StoreError>`.
|
||||
// // println!("Get int {:?}", store.get(&reader, "int").unwrap());
|
||||
// // println!("Get uint {:?}", store.get(&reader, "uint").unwrap());
|
||||
// // println!("Get float {:?}", store.get(&reader, "float").unwrap());
|
||||
// // println!("Get instant {:?}", store.get(&reader, "instant").unwrap());
|
||||
// // println!("Get boolean {:?}", store.get(&reader, "boolean").unwrap());
|
||||
// // println!("Get string {:?}", store.get(&reader, "string").unwrap());
|
||||
// // println!("Get json {:?}", store.get(&reader, "json").unwrap());
|
||||
// println!("Get blob {:?}", store.get(&reader, "blob").unwrap());
|
||||
|
||||
// // Retrieving a non-existent value returns `Ok(None)`.
|
||||
// println!(
|
||||
// "Get non-existent value {:?}",
|
||||
// store.get(&reader, "non-existent").unwrap()
|
||||
// );
|
||||
|
||||
// // A read transaction will automatically close once the reader goes out of scope,
|
||||
// // so isn't necessary to close it explicitly, although you can do so by calling
|
||||
// // `Reader.abort()`.
|
||||
// }
|
||||
|
||||
// {
|
||||
// // Aborting a write transaction rolls back the change(s).
|
||||
// let mut writer = env.write().unwrap();
|
||||
// store.put(&mut writer, "foo", &Value::Blob(b"bar")).unwrap();
|
||||
// writer.abort();
|
||||
// let reader = env.read().expect("reader");
|
||||
// println!(
|
||||
// "It should be None! ({:?})",
|
||||
// store.get(&reader, "foo").unwrap()
|
||||
// );
|
||||
// }
|
||||
|
||||
// {
|
||||
// // Explicitly aborting a transaction is not required unless an early abort is
|
||||
// // desired, since both read and write transactions will implicitly be aborted once
|
||||
// // they go out of scope.
|
||||
// {
|
||||
// let mut writer = env.write().unwrap();
|
||||
// store.put(&mut writer, "foo", &Value::Blob(b"bar")).unwrap();
|
||||
// }
|
||||
// let reader = env.read().expect("reader");
|
||||
// println!(
|
||||
// "It should be None! ({:?})",
|
||||
// store.get(&reader, "foo").unwrap()
|
||||
// );
|
||||
// }
|
||||
|
||||
// {
|
||||
// // Deleting a key/value pair also requires a write transaction.
|
||||
// let mut writer = env.write().unwrap();
|
||||
// store.put(&mut writer, "foo", &Value::Blob(b"bar")).unwrap();
|
||||
// store.put(&mut writer, "bar", &Value::Blob(b"baz")).unwrap();
|
||||
// store.delete(&mut writer, "foo").unwrap();
|
||||
|
||||
// // A write transaction also supports reading, and the version of the store that it
|
||||
// // reads includes the changes it has made regardless of the commit state of that
|
||||
// // transaction.
|
||||
// // In the code above, "foo" and "bar" were put into the store, then "foo" was
|
||||
// // deleted so only "bar" will return a result when the database is queried via the
|
||||
// // writer.
|
||||
// println!(
|
||||
// "It should be None! ({:?})",
|
||||
// store.get(&writer, "foo").unwrap()
|
||||
// );
|
||||
// println!("Get bar ({:?})", store.get(&writer, "bar").unwrap());
|
||||
|
||||
// // But a reader won't see that change until the write transaction is committed.
|
||||
// {
|
||||
// let reader = env.read().expect("reader");
|
||||
// println!("Get foo {:?}", store.get(&reader, "foo").unwrap());
|
||||
// println!("Get bar {:?}", store.get(&reader, "bar").unwrap());
|
||||
// }
|
||||
// writer.commit().unwrap();
|
||||
// {
|
||||
// let reader = env.read().expect("reader");
|
||||
// println!(
|
||||
// "It should be None! ({:?})",
|
||||
// store.get(&reader, "foo").unwrap()
|
||||
// );
|
||||
// println!("Get bar {:?}", store.get(&reader, "bar").unwrap());
|
||||
// }
|
||||
|
||||
// // Committing a transaction consumes the writer, preventing you from reusing it by
|
||||
// // failing at compile time with an error. This line would report "error[E0382]:
|
||||
// // borrow of moved value: `writer`".
|
||||
// // store.put(&mut writer, "baz", &Value::Str("buz")).unwrap();
|
||||
// }
|
||||
|
||||
// {
|
||||
// // Clearing all the entries in the store with a write transaction.
|
||||
// {
|
||||
// let mut writer = env.write().unwrap();
|
||||
// store.put(&mut writer, "foo", &Value::Blob(b"bar")).unwrap();
|
||||
// store.put(&mut writer, "bar", &Value::Blob(b"baz")).unwrap();
|
||||
// writer.commit().unwrap();
|
||||
// }
|
||||
|
||||
// // {
|
||||
// // let mut writer = env.write().unwrap();
|
||||
// // store.clear(&mut writer).unwrap();
|
||||
// // writer.commit().unwrap();
|
||||
// // }
|
||||
|
||||
// // {
|
||||
// // let reader = env.read().expect("reader");
|
||||
// // println!(
|
||||
// // "It should be None! ({:?})",
|
||||
// // store.get(&reader, "foo").unwrap()
|
||||
// // );
|
||||
// // println!(
|
||||
// // "It should be None! ({:?})",
|
||||
// // store.get(&reader, "bar").unwrap()
|
||||
// // );
|
||||
// // }
|
||||
// }
|
||||
|
||||
let stat = env.stat().unwrap(); |
||||
let info = env.info().unwrap(); |
||||
println!("LMDB info map_size : {}", info.map_size()); |
||||
println!("LMDB info last_pgno : {}", info.last_pgno()); |
||||
println!("LMDB info last_txnid : {}", info.last_txnid()); |
||||
println!("LMDB info max_readers : {}", info.max_readers()); |
||||
println!("LMDB info num_readers : {}", info.num_readers()); |
||||
println!("LMDB stat page_size : {}", stat.page_size()); |
||||
println!("LMDB stat depth : {}", stat.depth()); |
||||
println!("LMDB stat branch_pages : {}", stat.branch_pages()); |
||||
println!("LMDB stat leaf_pages : {}", stat.leaf_pages()); |
||||
println!("LMDB stat overflow_pages : {}", stat.overflow_pages()); |
||||
println!("LMDB stat entries : {}", stat.entries()); |
||||
} |
||||
// We reopen the env and data to see if it was well saved to disk.
|
||||
{ |
||||
let mut manager = Manager::<LmdbEnvironment>::singleton().write().unwrap(); |
||||
let shared_rkv = manager |
||||
.get_or_create(root.path(), |path| { |
||||
//Rkv::new::<Lmdb>(path) // use this instead to disable encryption
|
||||
Rkv::with_encryption_key_and_mapsize::<Lmdb>(path, key, 2 * 1024 * 1024 * 1024) |
||||
}) |
||||
.unwrap(); |
||||
let env = shared_rkv.read().unwrap(); |
||||
|
||||
println!("LMDB Version: {}", env.version()); |
||||
|
||||
let mut store = env.open_single("testdb", StoreOptions::default()).unwrap(); //StoreOptions::create()
|
||||
|
||||
{ |
||||
let reader = env.read().expect("reader"); |
||||
println!( |
||||
"It should be baz! ({:?})", |
||||
store.get(&reader, "bar").unwrap() |
||||
); |
||||
} |
||||
} |
||||
// Here the database and environment is closed, but the files are still present in the temp directory.
|
||||
// uncomment this if you need time to copy them somewhere for analysis, before the temp folder get destroyed
|
||||
//thread::sleep(Duration::from_millis(20000));
|
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
[package] |
||||
name = "p2p-verifier" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "P2P Verifier module of NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[dependencies] |
||||
p2p-repo = { path = "../p2p-repo" } |
||||
p2p-net = { path = "../p2p-net" } |
||||
blake3 = "1.3.1" |
||||
chacha20 = "0.9.0" |
||||
serde = { version = "1.0", features = ["derive"] } |
||||
serde_bare = "0.5.0" |
||||
serde_bytes = "0.11.7" |
Loading…
Reference in new issue