mirror of
https://github.com/vacp2p/nim-libp2p.git
synced 2026-01-10 08:08:03 -05:00
Compare commits
261 Commits
fuzzing
...
minimize_i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54a4054308 | ||
|
|
d6feb1bbc2 | ||
|
|
3f5b5cee75 | ||
|
|
8a4e8a00a2 | ||
|
|
77d40c34f4 | ||
|
|
2fa2c4425f | ||
|
|
0911cb20f4 | ||
|
|
3ca49a2f40 | ||
|
|
1b91b97499 | ||
|
|
21cbe3a91a | ||
|
|
88e233db81 | ||
|
|
84659af45b | ||
|
|
aef44ed1ce | ||
|
|
02c96fc003 | ||
|
|
c4da9be32c | ||
|
|
2b5319622c | ||
|
|
5cbb473d1b | ||
|
|
b30b2656d5 | ||
|
|
89cad5a3ba | ||
|
|
09b3e11956 | ||
|
|
03f67d3db5 | ||
|
|
bb97a9de79 | ||
|
|
1a707e1264 | ||
|
|
458b0885dd | ||
|
|
a2027003cd | ||
|
|
c5db35d9b0 | ||
|
|
d1e51beb7f | ||
|
|
275d649287 | ||
|
|
467b5b4f0c | ||
|
|
fdf53d18cd | ||
|
|
48a3ac06ff | ||
|
|
49a92e5641 | ||
|
|
08a48faf41 | ||
|
|
61b299e411 | ||
|
|
ca01ee06a8 | ||
|
|
6c43ab3fce | ||
|
|
ae13a0d583 | ||
|
|
28609597d1 | ||
|
|
8294d5b9df | ||
|
|
78e83889ee | ||
|
|
7603b8de5e | ||
|
|
8cccd54125 | ||
|
|
18e00a741b | ||
|
|
ee264fdf11 | ||
|
|
9059a8aced | ||
|
|
0b753e7cf2 | ||
|
|
d43c5feab0 | ||
|
|
1609fd7197 | ||
|
|
42cd78e95b | ||
|
|
44cada9c55 | ||
|
|
6c873481ac | ||
|
|
d08ce17144 | ||
|
|
bd6ead95ef | ||
|
|
53e3825e07 | ||
|
|
e9b456162a | ||
|
|
250024f6cc | ||
|
|
fec632d28d | ||
|
|
349496e40f | ||
|
|
7faa0fac23 | ||
|
|
c5e4f8e12d | ||
|
|
fe4ff79885 | ||
|
|
aa4ebb0b3c | ||
|
|
e0f70b7177 | ||
|
|
c1dfd58772 | ||
|
|
04af0c4323 | ||
|
|
eb0890cd6f | ||
|
|
9bc5ec1566 | ||
|
|
5594bcb33e | ||
|
|
d46bcdb6ac | ||
|
|
9468bb6b4d | ||
|
|
2725be64ba | ||
|
|
e3c967ad19 | ||
|
|
d2c98bd87d | ||
|
|
3011ba4326 | ||
|
|
c6566707fa | ||
|
|
3be681ec4d | ||
|
|
2ede0fa40c | ||
|
|
7c195ab927 | ||
|
|
3230407ffe | ||
|
|
deb72c8580 | ||
|
|
ce0685c272 | ||
|
|
1f4b090227 | ||
|
|
fb05f5ae22 | ||
|
|
e12f65f193 | ||
|
|
4b3bc4f819 | ||
|
|
6791f5e7bb | ||
|
|
08d9c84aca | ||
|
|
4e7eaba67a | ||
|
|
5f7a3ab829 | ||
|
|
ebef85c9d7 | ||
|
|
3fc1236659 | ||
|
|
fc4e9a8bb8 | ||
|
|
60f953629d | ||
|
|
18b0f726df | ||
|
|
459f6851e7 | ||
|
|
575344e2e9 | ||
|
|
75871817ee | ||
|
|
61929aed6c | ||
|
|
56599f5b9d | ||
|
|
b2eac7ecbd | ||
|
|
20b0e40f7d | ||
|
|
ff77d52851 | ||
|
|
545a31d4f0 | ||
|
|
b76bac752f | ||
|
|
c6aa085e98 | ||
|
|
e03547ea3e | ||
|
|
f80ce3133c | ||
|
|
d6263bf751 | ||
|
|
56c23a286a | ||
|
|
7a369dd1bf | ||
|
|
b784167805 | ||
|
|
440461b24b | ||
|
|
fab1340020 | ||
|
|
1721f078c7 | ||
|
|
74c402ed9d | ||
|
|
c45f9705ab | ||
|
|
81b861b34e | ||
|
|
43359dd9d1 | ||
|
|
f85d0f75ea | ||
|
|
66f9dc9167 | ||
|
|
1c4d0832ce | ||
|
|
224f92e172 | ||
|
|
5efa089196 | ||
|
|
9d4c4307de | ||
|
|
49dfa84c6f | ||
|
|
a65b7b028f | ||
|
|
67711478ce | ||
|
|
c28d8bb353 | ||
|
|
eb78292d9c | ||
|
|
3725f6a95b | ||
|
|
3640b4dd89 | ||
|
|
32085ca88a | ||
|
|
c76d1e18ef | ||
|
|
41649f0999 | ||
|
|
67102873ba | ||
|
|
d40d324160 | ||
|
|
a677b06273 | ||
|
|
6050cdef7e | ||
|
|
fedfa8e817 | ||
|
|
6887b43777 | ||
|
|
225accd11b | ||
|
|
7d6bc545e0 | ||
|
|
a1eb53b181 | ||
|
|
db629dca25 | ||
|
|
a5666789b0 | ||
|
|
b7726bf68f | ||
|
|
0221affe98 | ||
|
|
edbd35b16c | ||
|
|
80cca0ecac | ||
|
|
0041ed4cf8 | ||
|
|
95e98e8c51 | ||
|
|
4aa615c44c | ||
|
|
6b61ce8c91 | ||
|
|
53b060f8f0 | ||
|
|
af5299f26c | ||
|
|
bac754e2ad | ||
|
|
8d5ea43e2b | ||
|
|
e573238705 | ||
|
|
c1a3bd8fee | ||
|
|
ddeb7b3bd4 | ||
|
|
382b992e00 | ||
|
|
408dcf12bd | ||
|
|
0012b639c8 | ||
|
|
f7f1e89669 | ||
|
|
f14ada3dcf | ||
|
|
444b837923 | ||
|
|
f89bd0c77c | ||
|
|
e68186373b | ||
|
|
266c7b117a | ||
|
|
0e28d3b828 | ||
|
|
4ace70d53b | ||
|
|
ca19f8fdbf | ||
|
|
351bda2b56 | ||
|
|
7d9c43a5ce | ||
|
|
c11772c94e | ||
|
|
489c115132 | ||
|
|
166c0d1c87 | ||
|
|
ba451196e8 | ||
|
|
9f658c151e | ||
|
|
e304ad0f7e | ||
|
|
5e3323d43f | ||
|
|
9532bff983 | ||
|
|
676786b00e | ||
|
|
d521c57b82 | ||
|
|
63e1872516 | ||
|
|
67ef25fae0 | ||
|
|
fe7a69e389 | ||
|
|
a17cad710c | ||
|
|
3863a4cd21 | ||
|
|
64cbbe1e0a | ||
|
|
31ad4ae205 | ||
|
|
b3d9360dfc | ||
|
|
1711c204ea | ||
|
|
c43aacdc81 | ||
|
|
711609057c | ||
|
|
192cac6254 | ||
|
|
cc3c637c22 | ||
|
|
afbb1b4d3c | ||
|
|
8c2eca18dc | ||
|
|
ce371f3bb4 | ||
|
|
23338fceaa | ||
|
|
6ab6ab48ef | ||
|
|
d9305bda84 | ||
|
|
f95eda8bf6 | ||
|
|
a3e9d1ed80 | ||
|
|
c9c2f6acdb | ||
|
|
c39c1cbf68 | ||
|
|
b4f96721af | ||
|
|
4bce8f38c9 | ||
|
|
dc13ff81d3 | ||
|
|
a69301f392 | ||
|
|
4b105c6abd | ||
|
|
2e12c7ab73 | ||
|
|
a086fcba72 | ||
|
|
7b103e02f2 | ||
|
|
32233d36c8 | ||
|
|
1c99aca054 | ||
|
|
4f18dd30e9 | ||
|
|
0cd3554ce4 | ||
|
|
bcb8f5e3b6 | ||
|
|
eb78660702 | ||
|
|
103e199bc0 | ||
|
|
fa5d102370 | ||
|
|
a56c3bc296 | ||
|
|
5e7e009445 | ||
|
|
72abe822c0 | ||
|
|
a001508490 | ||
|
|
4d8b50d24c | ||
|
|
ef594e1e02 | ||
|
|
d8a9e93ff7 | ||
|
|
abbeaab684 | ||
|
|
dfbfbe6eb6 | ||
|
|
1de7508b64 | ||
|
|
3ffc03ed16 | ||
|
|
543358b262 | ||
|
|
14d2c3f51e | ||
|
|
2332813873 | ||
|
|
124a7a5ffe | ||
|
|
2d864633ea | ||
|
|
2fbe82bf9d | ||
|
|
20c02a5f23 | ||
|
|
a9a7e7eb15 | ||
|
|
34c2fb8787 | ||
|
|
1e598a0239 | ||
|
|
4ca1c2d7ed | ||
|
|
83ad890535 | ||
|
|
0b0686ee94 | ||
|
|
93ac795aef | ||
|
|
912873f8b3 | ||
|
|
78a65eebcc | ||
|
|
533e39ef94 | ||
|
|
150fafbee8 | ||
|
|
d0523fdc9d | ||
|
|
0ece5eaf12 | ||
|
|
e6440c43c2 | ||
|
|
597abddba7 | ||
|
|
5d7024f2e0 | ||
|
|
a7e335e1bb | ||
|
|
718374d890 | ||
|
|
36f3132d9a | ||
|
|
ca3f4e8701 |
1141
.assets/full-logo.svg
Normal file
1141
.assets/full-logo.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 61 KiB |
96
.assets/small-logo.svg
Normal file
96
.assets/small-logo.svg
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:v="https://vecta.io/nano" xmlns:xlink="http://www.w3.org/1999/xlink" width="172.071" height="196.414" viewBox="0 0 45.527 51.968">
|
||||
<g transform="matrix(.2822 0 0 .2822 -212.833275 -150.656248)">
|
||||
<path d="M835.432 533.821l-12.483 9.783c-6.482-.207-19.197 1.251-26.086 3.769-6.346-4.04-11.923-8.5-11.923-8.5l-7.762 13.071c-4.444 2.375-8.906 5.046-12.883 8.58l-10.162-4.17c6.125 12.414 10.243 24.844 21.445 32.316 17.834-28.299 100.705-25.691 118.907-.16 11.764-6.165 16.339-19.429 20.965-31.674-.507.168-6.802 2.285-10.882 3.849-2.436-2.665-8.179-6.763-11.443-8.741-3.096-5.696-7.602-13.391-7.602-13.391s-5.337 3.988-11.523 8.34c-8.357-1.55-18.465-3.433-26.966-2.967-5.787-4.779-11.603-10.104-11.603-10.104z" fill="#f3d400" />
|
||||
<g opacity=".9" transform="matrix(.9375 0 0 .9375 765.1166 550.13225)">
|
||||
<path d="M99.952 106.898l.215-.107 24.755-14.248-24.97-14.535-24.97 14.374z" fill="#cc2a65" />
|
||||
<use xlink:href="#B" fill="#a21d4c" />
|
||||
<path d="M124.922 92.542l-24.755 14.248-.215.107v28.89l24.97-14.356z" fill="#b62454" />
|
||||
<path d="M50.012 106.737l.215-.125 24.755-14.248-24.97-14.517-24.97 14.356z" fill="#c8d92b" />
|
||||
<path d="M50.012 135.609v-28.872l-24.97-14.535v28.89h.018z" fill="#c2d02f" />
|
||||
<path d="M74.982 92.381l-24.755 14.23-.215.125v28.872.018h.018l24.952-14.356v-.018z" fill="#b9be33" />
|
||||
<path d="M74.982 121.253l.215-.107 24.755-14.248-24.97-14.535-24.97 14.374z" fill="#cc2a65" />
|
||||
<use xlink:href="#B" x="-24.97" y="14.356" fill="#a21d4c" />
|
||||
<path d="M99.952 106.898l-24.755 14.248-.215.107v28.89H75l24.952-14.356z" fill="#b62454" />
|
||||
<path d="M124.905 121.415l.215-.125 24.737-14.23-24.952-14.535-24.97 14.356z" fill="#a159a2" />
|
||||
<path d="M124.905 150.305v-28.89l-24.97-14.535v28.89h.018z" fill="#772a86" />
|
||||
<path d="M149.875 107.059l-24.755 14.23-.215.125v28.89l24.97-14.356z" fill="#8e3b95" />
|
||||
<path d="M74.982 92.345l.215-.125 24.737-14.248-24.952-14.517-24.97 14.356z" fill="#bec831" />
|
||||
<path d="M74.982 121.217V92.345L50.012 77.81v28.89h.018z" fill="#a1a938" />
|
||||
<path d="M99.952 77.989l-24.755 14.23-.215.125v28.872.018l24.97-14.356v-.018z" fill="#999b37" />
|
||||
<path d="M75 60.645l.197-.125 24.755-14.23L75 31.755 50.029 46.11l24.952 14.535z" fill="#bec831" />
|
||||
<path d="M74.982 89.535L75 60.645 50.029 46.11 50.012 75h.018z" fill="#a1a938" />
|
||||
<path d="M99.97 46.307L75.197 60.52l-.197.125h-.018v28.89l24.97-14.338v-.018z" fill="#999b37" />
|
||||
<path d="M99.952 75.179l.215-.107 24.755-14.23L99.97 46.306 75 60.644z" fill="#ee539a" />
|
||||
<path d="M99.952 104.069v-28.89L75 60.644l-.018 28.89H75z" fill="#d01b68" />
|
||||
<path d="M124.922 60.841l-24.755 14.23-.215.107v28.89l24.97-14.338.018-28.89z" fill="#ec0f68" />
|
||||
<path d="M124.923 89.731l.215-.125 24.755-14.23-24.952-14.535h-.018l-24.97 14.338z" fill="#a159a2" />
|
||||
<path d="M124.905 118.622l.018-28.89-24.97-14.535v28.872.018z" fill="#772a86" />
|
||||
<path d="M149.893 75.376l-24.755 14.23-.215.125-.018 28.89h.018l24.97-14.356z" fill="#8e3b95" />
|
||||
<path d="M50.03 75l.197-.125 24.755-14.23L50.03 46.109 25.06 60.447l24.952 14.535z" fill="#c8d92b" />
|
||||
<path d="M50.012 103.872L50.03 75 25.06 60.447l-.018 28.89h.018z" fill="#c2d02f" />
|
||||
<path d="M75 60.644l-24.773 14.23-.197.125-.018 28.872 24.97-14.338z" fill="#b9be33" />
|
||||
<path d="M74.982 89.534l.215-.125 24.755-14.23L75 60.644l-24.97 14.338z" fill="#f7af19" />
|
||||
<path d="M74.982 118.425v-.018.018-28.89L50.029 75l-.018 28.872.018.018z" fill="#f2901f" />
|
||||
<path d="M99.952 75.179l-24.755 14.23-.215.125v28.89l24.97-14.356.018-28.89z" fill="#f9a120" />
|
||||
<path d="M99.934 135.769l.215-.125 24.684-14.356-25.042-14.409L74.91 121.36z" fill="#833593" />
|
||||
<path d="M100.077 164.66l-.143-28.89-25.042-14.409.143 28.89h.018z" fill="#652977" />
|
||||
<path d="M124.833 121.288l-24.684 14.356-.215.125.143 28.89 24.899-14.481z" fill="#4d1f5b" />
|
||||
<path d="M99.952 104.069l.215-.107 24.755-14.23L99.97 75.179h-.018l-24.97 14.356z" fill="#a159a2" />
|
||||
<path d="M99.934 132.959l.018-28.89-24.97-14.535v28.89z" fill="#772a86" />
|
||||
<path d="M124.922 89.732l-24.755 14.23-.215.107-.018 28.89h.018l24.97-14.338z" fill="#8e3b95" />
|
||||
<path d="M25.042 121.074l.197-.125 24.755-14.248-24.952-14.517h-.018L.071 106.54l24.952 14.535z" fill="#f6dd03" />
|
||||
<path d="M25.024 149.947h.018v-28.872L.071 106.54v28.89z" fill="#f9bb1d" />
|
||||
<path d="M49.994 106.719l-24.755 14.23-.197.125v28.872.018l24.952-14.356h.018v-.018-28.872z" fill="#e9ae20" />
|
||||
<path d="M25.06 89.338l.197-.125 24.755-14.23L25.06 60.447.089 74.803l24.952 14.535z" fill="#f6dd03" />
|
||||
<path d="M25.042 118.228l.018-28.89L.089 74.803.072 103.675l.018.018z" fill="#f9bb1d" />
|
||||
<path d="M50.03 75L25.257 89.212l-.197.125-.018 28.89 24.97-14.356v.018-.018z" fill="#e9ae20" />
|
||||
<path d="M50.012 135.59l.215-.107 24.737-14.248L50.012 106.7l-24.97 14.374z" fill="#f7af19" />
|
||||
<path d="M50.012 164.481v-28.89l-24.97-14.517v28.872.018z" fill="#f2901f" />
|
||||
<path d="M74.964 121.235l-24.755 14.248-.197.107v28.89l24.97-14.356v-28.89z" fill="#f9a120" />
|
||||
<path d="M50.012 103.872l.215-.107 24.755-14.23L50.03 74.982 25.06 89.338z" fill="#f7af19" />
|
||||
<path d="M50.012 132.763v-28.89L25.06 89.338l-.018 28.89h.018z" fill="#f2901f" />
|
||||
<path d="M74.982 89.535l-24.755 14.23-.215.107v28.89l24.97-14.338.018-28.89z" fill="#f9a120" />
|
||||
<path d="M74.982 150.125l.197-.125 24.755-14.23-24.952-14.535h-.018l-24.952 14.356 24.952 14.535z" fill="#f7af19" />
|
||||
<path d="M74.964 179.015h.018v-28.89l-24.97-14.517v28.872.018z" fill="#f2901f" />
|
||||
<path d="M99.934 135.77L75.179 150l-.197.125v28.89l24.97-14.356v-28.89z" fill="#f9a120" />
|
||||
<path d="M74.982 118.425l.215-.125 24.755-14.23L75 89.535h-.018l-24.97 14.338z" fill="#31838b" />
|
||||
<path d="M74.964 147.297l.018-28.872-24.97-14.535v-.018 28.89.018z" fill="#22626c" />
|
||||
<path d="M99.952 104.069L75.197 118.3l-.215.125-.018 28.872v.018h.018l24.97-14.356z" fill="#1b4b56" />
|
||||
<path d="M74.982 28.962l.215-.125 24.737-14.248L74.982.072l-24.97 14.356 24.97 14.517z" fill="#bec831" />
|
||||
<path d="M74.982 57.834V28.962l-24.97-14.535v28.89h.018z" fill="#a1a938" />
|
||||
<path d="M99.952 14.606l-24.755 14.23-.215.125v28.872l24.97-14.356z" fill="#999b37" />
|
||||
<path d="M74.964 28.944l.215-.125 24.755-14.23L74.982.054h-.018l-24.97 14.338z" fill="#a159a2" />
|
||||
<path d="M74.946 57.835l.018-28.89-24.97-14.535v28.872.018z" fill="#772a86" />
|
||||
<path d="M99.934 14.589l-24.755 14.23-.215.125-.018 28.89h.018l24.97-14.356z" fill="#8e3b95" />
|
||||
<path d="M99.952 43.479l.215-.107 24.755-14.248-24.97-14.535-24.97 14.356z" fill="#ee539a" />
|
||||
<use xlink:href="#B" y="-63.419" fill="#d01b68" />
|
||||
<path d="M124.922 29.123l-24.755 14.248-.215.107v28.89l24.97-14.356z" fill="#ec0f68" />
|
||||
<path d="M50.03 43.317l.215-.125L75 28.961 50.048 14.427h-.018L25.06 28.765z" fill="#31838b" />
|
||||
<path d="M50.012 72.189l.018-28.872-24.97-14.535v-.018 28.89.018z" fill="#22626c" />
|
||||
<path d="M75 28.961l-24.755 14.23-.215.125-.018 28.872v.018h.018L75 57.852z" fill="#1b4b56" />
|
||||
<path d="M124.923 58.013l.215-.125 24.737-14.23-24.952-14.535-24.97 14.356z" fill="#cc2a65" />
|
||||
<use xlink:href="#B" x="24.971" y="-48.884" fill="#a21d4c" />
|
||||
<path d="M149.893 43.658l-24.755 14.23-.215.125v28.89l24.97-14.356z" fill="#b62454" />
|
||||
<path d="M74.982 57.835l.215-.107 24.755-14.248-24.97-14.535L50.012 43.3z" fill="#c8d92b" />
|
||||
<path d="M74.982 86.725v-28.89l-24.97-14.517V72.19l.018.018z" fill="#c2d02f" />
|
||||
<path d="M99.952 43.479L75.197 57.727l-.215.107v28.89H75l24.952-14.356z" fill="#b9be33" />
|
||||
<path d="M99.952 72.369l.215-.125 24.755-14.23-24.97-14.535-24.97 14.356z" fill="#33b4d7" />
|
||||
<use xlink:href="#B" y="-34.529" fill="#209ac5" />
|
||||
<path d="M124.922 58.014l-24.755 14.23-.215.125v28.89h.018l24.952-14.356z" fill="#0f8cae" />
|
||||
<path d="M25.06 57.673l.197-.125L50.012 43.3 25.06 28.783h-.018L.089 43.139l24.952 14.535z" fill="#94d6e3" />
|
||||
<path d="M25.042 86.546h.018V57.673L.089 43.139v28.89z" fill="#73ccdd" />
|
||||
<path d="M50.012 43.318l-24.755 14.23-.197.125v28.872.018l24.952-14.356h.018v-.018-28.872z" fill="#3bafbb" />
|
||||
<path d="M50.03 72.19l.215-.107 24.737-14.248L50.03 43.318 25.06 57.674z" fill="#94d6e3" />
|
||||
<path d="M50.03 101.08V72.208v-.018L25.06 57.674v28.872.018z" fill="#73ccdd" />
|
||||
<path d="M74.982 57.835L50.227 72.083l-.197.107v28.89L75 86.725v-28.89z" fill="#3bafbb" />
|
||||
<path d="M75 86.724l.197-.107 24.755-14.248L75 57.834h-.018L50.029 72.189l24.952 14.535z" fill="#33b4d7" />
|
||||
<path d="M74.982 115.614H75v-28.89l-24.97-14.517v28.872.018z" fill="#209ac5" />
|
||||
<path d="M99.952 72.368L75.197 86.617l-.197.107v28.89l24.97-14.356v-28.89z" fill="#0f8cae" />
|
||||
</g>
|
||||
<path d="M759.126 567.007s10.273 21.02 16.364 35.698c25.549 33.869 90.792 36.224 119.235.656 9.484-17.619 16.733-36.357 16.733-36.357-7.297 10.862-20.094 18.056-27.408 22.095-5.197 2.861-17.189 4.59-17.189 4.59l-31.482-16.393-31.663 16.065s-11.832-1.91-17.189-4.426c-10.811-5.799-19.735-12.549-27.401-21.928z" fill="#ffe953" />
|
||||
</g>
|
||||
<defs>
|
||||
<path id="B" d="M99.952 135.788v-28.89l-24.97-14.517v28.872l.018.018z" />
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
131
.github/actions/install_nim/action.yml
vendored
Normal file
131
.github/actions/install_nim/action.yml
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
name: Install Nim
|
||||
inputs:
|
||||
os:
|
||||
description: "Operating system to build for"
|
||||
required: true
|
||||
cpu:
|
||||
description: "CPU to build for"
|
||||
default: "amd64"
|
||||
nim_branch:
|
||||
description: "Nim version"
|
||||
default: "version-1-6"
|
||||
shell:
|
||||
description: "Shell to run commands in"
|
||||
default: "bash --noprofile --norc -e -o pipefail"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install build dependencies (Linux i386)
|
||||
shell: ${{ inputs.shell }}
|
||||
if: inputs.os == 'Linux' && inputs.cpu == 'i386'
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND='noninteractive' apt-get install \
|
||||
--no-install-recommends -yq gcc-multilib g++-multilib \
|
||||
libssl-dev:i386
|
||||
mkdir -p external/bin
|
||||
cat << EOF > external/bin/gcc
|
||||
#!/bin/bash
|
||||
exec $(which gcc) -m32 "\$@"
|
||||
EOF
|
||||
cat << EOF > external/bin/g++
|
||||
#!/bin/bash
|
||||
exec $(which g++) -m32 "\$@"
|
||||
EOF
|
||||
chmod 755 external/bin/gcc external/bin/g++
|
||||
echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: MSYS2 (Windows i386)
|
||||
if: inputs.os == 'Windows' && inputs.cpu == 'i386'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
msystem: MINGW32
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-i686-toolchain
|
||||
|
||||
- name: MSYS2 (Windows amd64)
|
||||
if: inputs.os == 'Windows' && inputs.cpu == 'amd64'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-x86_64-toolchain
|
||||
|
||||
- name: Restore Nim DLLs dependencies (Windows) from cache
|
||||
if: inputs.os == 'Windows'
|
||||
id: windows-dlls-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: external/dlls
|
||||
key: 'dlls'
|
||||
|
||||
- name: Install DLL dependencies (Windows)
|
||||
shell: ${{ inputs.shell }}
|
||||
if: >
|
||||
steps.windows-dlls-cache.outputs.cache-hit != 'true' &&
|
||||
inputs.os == 'Windows'
|
||||
run: |
|
||||
mkdir external
|
||||
curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip
|
||||
7z x external/windeps.zip -oexternal/dlls
|
||||
|
||||
- name: Path to cached dependencies (Windows)
|
||||
shell: ${{ inputs.shell }}
|
||||
if: >
|
||||
inputs.os == 'Windows'
|
||||
run: |
|
||||
echo '${{ github.workspace }}'"/external/dlls" >> $GITHUB_PATH
|
||||
|
||||
- name: Derive environment variables
|
||||
shell: ${{ inputs.shell }}
|
||||
run: |
|
||||
if [[ '${{ inputs.cpu }}' == 'amd64' ]]; then
|
||||
PLATFORM=x64
|
||||
else
|
||||
PLATFORM=x86
|
||||
fi
|
||||
echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
|
||||
|
||||
ncpu=
|
||||
MAKE_CMD="make"
|
||||
case '${{ inputs.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
MAKE_CMD="mingw32-make"
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
echo "ncpu=$ncpu" >> $GITHUB_ENV
|
||||
echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV
|
||||
echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: Restore Nim from cache
|
||||
id: nim-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '${{ github.workspace }}/nim'
|
||||
key: ${{ inputs.os }}-${{ inputs.cpu }}-nim-${{ inputs.nim_branch }}-cache-${{ env.cache_nonce }}
|
||||
|
||||
- name: Build Nim and Nimble
|
||||
shell: ${{ inputs.shell }}
|
||||
if: ${{ steps.nim-cache.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
# We don't want partial matches of the cache restored
|
||||
rm -rf nim
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ inputs.nim_branch }} \
|
||||
QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \
|
||||
bash build_nim.sh nim csources dist/nimble NimBinaries
|
||||
44
.github/workflows/bumper.yml
vendored
Normal file
44
.github/workflows/bumper.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Bumper
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
bumpProjects:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [
|
||||
{ repo: status-im/nimbus-eth2, branch: unstable },
|
||||
{ repo: waku-org/nwaku, branch: master },
|
||||
{ repo: codex-storage/nim-codex, branch: master }
|
||||
]
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ matrix.target.repo }}
|
||||
ref: ${{ matrix.target.branch }}
|
||||
path: nbc
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||
|
||||
- name: Checkout this ref
|
||||
run: |
|
||||
cd nbc
|
||||
git submodule update --init vendor/nim-libp2p
|
||||
cd vendor/nim-libp2p
|
||||
git checkout $GITHUB_SHA
|
||||
|
||||
- name: Commit this bump
|
||||
run: |
|
||||
cd nbc
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit --allow-empty -a -m "auto-bump nim-libp2p"
|
||||
git branch -D nim-libp2p-auto-bump-${GITHUB_REF##*/} || true
|
||||
git switch -c nim-libp2p-auto-bump-${GITHUB_REF##*/}
|
||||
git push -f origin nim-libp2p-auto-bump-${GITHUB_REF##*/}
|
||||
213
.github/workflows/ci.yml
vendored
213
.github/workflows/ci.yml
vendored
@@ -7,6 +7,10 @@ on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 90
|
||||
@@ -24,7 +28,7 @@ jobs:
|
||||
cpu: amd64
|
||||
#- os: windows
|
||||
#cpu: i386
|
||||
branch: [version-1-2, devel]
|
||||
branch: [version-1-6]
|
||||
include:
|
||||
- target:
|
||||
os: linux
|
||||
@@ -32,7 +36,7 @@ jobs:
|
||||
shell: bash
|
||||
- target:
|
||||
os: macos
|
||||
builder: macos-10.15
|
||||
builder: macos-12
|
||||
shell: bash
|
||||
- target:
|
||||
os: windows
|
||||
@@ -45,111 +49,20 @@ jobs:
|
||||
|
||||
name: '${{ matrix.target.os }}-${{ matrix.target.cpu }} (Nim ${{ matrix.branch }})'
|
||||
runs-on: ${{ matrix.builder }}
|
||||
continue-on-error: ${{ matrix.branch == 'version-1-6' || matrix.branch == 'devel' }}
|
||||
continue-on-error: ${{ matrix.branch == 'devel' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install build dependencies (Linux i386)
|
||||
if: runner.os == 'Linux' && matrix.target.cpu == 'i386'
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND='noninteractive' apt-get install \
|
||||
--no-install-recommends -yq gcc-multilib g++-multilib \
|
||||
libssl-dev:i386
|
||||
mkdir -p external/bin
|
||||
cat << EOF > external/bin/gcc
|
||||
#!/bin/bash
|
||||
exec $(which gcc) -m32 "\$@"
|
||||
EOF
|
||||
cat << EOF > external/bin/g++
|
||||
#!/bin/bash
|
||||
exec $(which g++) -m32 "\$@"
|
||||
EOF
|
||||
chmod 755 external/bin/gcc external/bin/g++
|
||||
echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: MSYS2 (Windows i386)
|
||||
if: runner.os == 'Windows' && matrix.target.cpu == 'i386'
|
||||
uses: msys2/setup-msys2@v2
|
||||
- name: Setup Nim
|
||||
uses: "./.github/actions/install_nim"
|
||||
with:
|
||||
path-type: inherit
|
||||
msystem: MINGW32
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-i686-toolchain
|
||||
|
||||
- name: MSYS2 (Windows amd64)
|
||||
if: runner.os == 'Windows' && matrix.target.cpu == 'amd64'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-x86_64-toolchain
|
||||
|
||||
- name: Restore Nim DLLs dependencies (Windows) from cache
|
||||
if: runner.os == 'Windows'
|
||||
id: windows-dlls-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: external/dlls
|
||||
key: 'dlls'
|
||||
|
||||
- name: Install DLL dependencies (Windows)
|
||||
if: >
|
||||
steps.windows-dlls-cache.outputs.cache-hit != 'true' &&
|
||||
runner.os == 'Windows'
|
||||
run: |
|
||||
mkdir external
|
||||
curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip
|
||||
7z x external/windeps.zip -oexternal/dlls
|
||||
|
||||
- name: Path to cached dependencies (Windows)
|
||||
if: >
|
||||
runner.os == 'Windows'
|
||||
run: |
|
||||
echo '${{ github.workspace }}'"/external/dlls" >> $GITHUB_PATH
|
||||
|
||||
- name: Derive environment variables
|
||||
run: |
|
||||
if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then
|
||||
PLATFORM=x64
|
||||
else
|
||||
PLATFORM=x86
|
||||
fi
|
||||
echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
|
||||
|
||||
ncpu=
|
||||
MAKE_CMD="make"
|
||||
case '${{ runner.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
MAKE_CMD="mingw32-make"
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
echo "ncpu=$ncpu" >> $GITHUB_ENV
|
||||
echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Nim and Nimble
|
||||
run: |
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ matrix.branch }} \
|
||||
QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \
|
||||
bash build_nim.sh nim csources dist/nimble NimBinaries
|
||||
echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH
|
||||
os: ${{ matrix.target.os }}
|
||||
cpu: ${{ matrix.target.cpu }}
|
||||
shell: ${{ matrix.shell }}
|
||||
nim_branch: ${{ matrix.branch }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
@@ -160,96 +73,20 @@ jobs:
|
||||
run: |
|
||||
V=1 bash scripts/build_p2pd.sh p2pdCache 124530a3
|
||||
|
||||
- name: Restore deps from cache
|
||||
id: deps-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: nimbledeps
|
||||
key: nimbledeps-${{ hashFiles('.pinned') }}
|
||||
|
||||
- name: Install deps
|
||||
if: ${{ steps.deps-cache.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
nimble install_pinned
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [[ "${{ matrix.target.os }}" == "windows" ]]; then
|
||||
# https://github.com/status-im/nimbus-eth2/issues/3121
|
||||
export NIMFLAGS="-d:nimRawSetjmp"
|
||||
fi
|
||||
nim --version
|
||||
nimble --version
|
||||
nimble install_pinned
|
||||
nimble test
|
||||
|
||||
bumpNBC-stable:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: status-im/github-app-token@v1
|
||||
name: Generate token
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.BUMP_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.BUMP_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Clone NBC
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: status-im/nimbus-eth2
|
||||
ref: unstable
|
||||
path: nbc
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout this ref
|
||||
run: |
|
||||
cd nbc/vendor/nim-libp2p
|
||||
git checkout $GITHUB_SHA
|
||||
|
||||
- name: Commit this bump
|
||||
run: |
|
||||
cd nbc
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit -a -m "auto-bump nim-libp2p"
|
||||
|
||||
- name: Make PR
|
||||
uses: peter-evans/create-pull-request@v3.5.0
|
||||
with:
|
||||
branch: nim-libp2p-auto-bump
|
||||
path: nbc
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
title: nim-libp2p auto bump
|
||||
|
||||
bumpNBC-unstable:
|
||||
if: github.ref == 'refs/heads/unstable'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: status-im/github-app-token@v1
|
||||
name: Generate token
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.BUMP_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.BUMP_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Clone NBC
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: status-im/nimbus-eth2
|
||||
ref: unstable
|
||||
path: nbc
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout this ref
|
||||
run: |
|
||||
cd nbc/vendor/nim-libp2p
|
||||
git checkout $GITHUB_SHA
|
||||
|
||||
- name: Commit this bump
|
||||
run: |
|
||||
cd nbc
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit -a -m "auto-bump nim-libp2p"
|
||||
|
||||
- name: Make PR
|
||||
uses: peter-evans/create-pull-request@v3.5.0
|
||||
with:
|
||||
branch: nim-libp2p-auto-bump-unstable
|
||||
path: nbc
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
title: nim-libp2p unstable auto bump
|
||||
draft: true
|
||||
|
||||
149
.github/workflows/codecov.yml
vendored
149
.github/workflows/codecov.yml
vendored
@@ -5,130 +5,61 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- unstable
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
GossipSub:
|
||||
Coverage:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
nim-options: [
|
||||
"",
|
||||
"-d:libp2p_pubsub_anonymize=true -d:libp2p_pubsub_sign=false -d:libp2p_pubsub_verify=false",
|
||||
"-d:libp2p_pubsub_sign=true -d:libp2p_pubsub_verify=true"
|
||||
]
|
||||
test-program: [
|
||||
"tests/pubsub/testpubsub",
|
||||
"tests/pubsub/testfloodsub",
|
||||
"tests/pubsub/testgossipinternal"
|
||||
]
|
||||
env:
|
||||
CICOV: YES
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Nim
|
||||
uses: "./.github/actions/install_nim"
|
||||
with:
|
||||
os: linux
|
||||
cpu: amd64
|
||||
shell: bash
|
||||
|
||||
- name: Restore deps from cache
|
||||
id: deps-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: nimbledeps
|
||||
key: nimbledeps-${{ hashFiles('.pinned') }}
|
||||
|
||||
- name: Install deps
|
||||
if: ${{ steps.deps-cache.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
nimble install_pinned
|
||||
|
||||
- name: Run
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lcov build-essential git curl
|
||||
mkdir coverage
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="make -j${NPROC}" bash build_nim.sh Nim csources dist/nimble NimBinaries
|
||||
export PATH="$PATH:$PWD/Nim/bin"
|
||||
nimble install_pinned
|
||||
export NIM_OPTIONS="--opt:speed -d:debug --verbosity:0 --hints:off --lineDir:on -d:chronicles_log_level=INFO --warning[CaseTransition]:off --warning[ObservableStores]:off --warning[LockLevel]:off --nimcache:nimcache --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage ${{ matrix.nim-options }}"
|
||||
nim c $NIM_OPTIONS -r ${{ matrix.test-program }}
|
||||
cd nimcache; rm *.c; cd ..
|
||||
export NIMFLAGS="--lineDir:on --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage"
|
||||
nimble testnative
|
||||
nimble testpubsub
|
||||
nimble testfilter
|
||||
find nimcache -name *.c -delete
|
||||
lcov --capture --directory nimcache --output-file coverage/coverage.info
|
||||
shopt -s globstar
|
||||
ls `pwd`/libp2p/{*,**/*}.nim
|
||||
lcov --extract coverage/coverage.info `pwd`/libp2p/{*,**/*}.nim --output-file coverage/coverage.f.info
|
||||
export COV_UUID=`cksum <<< "${{ matrix.test-program }} $NIM_OPTIONS" | cut -f 1 -d ' '`
|
||||
genhtml coverage/coverage.f.info --output-directory coverage/$COV_UUID-output
|
||||
echo ${{ matrix.test-program }} > coverage/$COV_UUID-nim_options.txt
|
||||
echo $NIM_OPTIONS >> coverage/$COV_UUID-nim_options.txt
|
||||
genhtml coverage/coverage.f.info --output-directory coverage/output
|
||||
bash <(curl -s https://codecov.io/bash) -f coverage/coverage.f.info || echo "Codecov did not collect coverage reports"
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
Tests:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
nim-options: [
|
||||
""
|
||||
]
|
||||
test-program: [
|
||||
"tests/testnative",
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lcov build-essential git curl
|
||||
mkdir coverage
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="make -j${NPROC}" bash build_nim.sh Nim csources dist/nimble NimBinaries
|
||||
export PATH="$PATH:$PWD/Nim/bin"
|
||||
nimble install_pinned
|
||||
export NIM_OPTIONS="--opt:speed -d:debug --verbosity:0 --hints:off --lineDir:on -d:chronicles_log_level=INFO --warning[CaseTransition]:off --warning[ObservableStores]:off --warning[LockLevel]:off --nimcache:nimcache --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage ${{ matrix.nim-options }} --clearNimblePath --NimblePath:nimbledeps/pkgs"
|
||||
nim c $NIM_OPTIONS -r ${{ matrix.test-program }}
|
||||
cd nimcache; rm *.c; cd ..
|
||||
lcov --capture --directory nimcache --output-file coverage/coverage.info
|
||||
shopt -s globstar
|
||||
ls `pwd`/libp2p/{*,**/*}.nim
|
||||
lcov --extract coverage/coverage.info `pwd`/libp2p/{*,**/*}.nim --output-file coverage/coverage.f.info
|
||||
export COV_UUID=`cksum <<< "${{ matrix.test-program }} $NIM_OPTIONS" | cut -f 1 -d ' '`
|
||||
genhtml coverage/coverage.f.info --output-directory coverage/$COV_UUID-output
|
||||
echo ${{ matrix.test-program }} > coverage/$COV_UUID-nim_options.txt
|
||||
echo $NIM_OPTIONS >> coverage/$COV_UUID-nim_options.txt
|
||||
bash <(curl -s https://codecov.io/bash) -f coverage/coverage.f.info || echo "Codecov did not collect coverage reports"
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
Filter:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
nim-options: [
|
||||
"",
|
||||
"-d:libp2p_pki_schemes=secp256k1",
|
||||
"-d:libp2p_pki_schemes=secp256k1;ed25519",
|
||||
"-d:libp2p_pki_schemes=secp256k1;ed25519;ecnist",
|
||||
]
|
||||
test-program: [
|
||||
"tests/testpkifilter",
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lcov build-essential git curl
|
||||
mkdir coverage
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="make -j${NPROC}" bash build_nim.sh Nim csources dist/nimble NimBinaries
|
||||
export PATH="$PATH:$PWD/Nim/bin"
|
||||
nimble install_pinned
|
||||
export NIM_OPTIONS="--opt:speed -d:debug --verbosity:0 --hints:off --lineDir:on -d:chronicles_log_level=INFO --warning[CaseTransition]:off --warning[ObservableStores]:off --warning[LockLevel]:off --nimcache:nimcache --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage ${{ matrix.nim-options }}"
|
||||
nim c $NIM_OPTIONS -r ${{ matrix.test-program }}
|
||||
cd nimcache; rm *.c; cd ..
|
||||
lcov --capture --directory nimcache --output-file coverage/coverage.info
|
||||
shopt -s globstar
|
||||
ls `pwd`/libp2p/{*,**/*}.nim
|
||||
lcov --extract coverage/coverage.info `pwd`/libp2p/{*,**/*}.nim --output-file coverage/coverage.f.info
|
||||
export COV_UUID=`cksum <<< "${{ matrix.test-program }} $NIM_OPTIONS" | cut -f 1 -d ' '`
|
||||
genhtml coverage/coverage.f.info --output-directory coverage/$COV_UUID-output
|
||||
echo ${{ matrix.test-program }} > coverage/$COV_UUID-nim_options.txt
|
||||
echo $NIM_OPTIONS >> coverage/$COV_UUID-nim_options.txt
|
||||
bash <(curl -s https://codecov.io/bash) -f coverage/coverage.f.info || echo "Codecov did not collect coverage reports"
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
|
||||
|
||||
#- uses: actions/upload-artifact@master
|
||||
# with:
|
||||
# name: coverage
|
||||
# path: coverage
|
||||
|
||||
12
.github/workflows/daily.yml
vendored
Normal file
12
.github/workflows/daily.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Daily
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-multi-nim-common:
|
||||
uses: ./.github/workflows/daily_common.yml
|
||||
with:
|
||||
nim-branch: "['version-1-6','version-2-0']"
|
||||
cpu: "['amd64']"
|
||||
84
.github/workflows/daily_common.yml
vendored
Normal file
84
.github/workflows/daily_common.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: daily-common
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
nim-branch:
|
||||
description: 'Nim branch'
|
||||
required: true
|
||||
type: string
|
||||
cpu:
|
||||
description: 'CPU'
|
||||
required: true
|
||||
type: string
|
||||
exclude:
|
||||
description: 'Exclude matrix configurations'
|
||||
required: false
|
||||
type: string
|
||||
default: "[]"
|
||||
|
||||
jobs:
|
||||
delete-cache:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: snnaplab/delete-branch-cache-action@v1
|
||||
|
||||
build:
|
||||
needs: delete-cache
|
||||
timeout-minutes: 120
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- os: linux
|
||||
builder: ubuntu-20
|
||||
shell: bash
|
||||
- os: macos
|
||||
builder: macos-12
|
||||
shell: bash
|
||||
- os: windows
|
||||
builder: windows-2019
|
||||
shell: msys2 {0}
|
||||
branch: ${{ fromJSON(inputs.nim-branch) }}
|
||||
cpu: ${{ fromJSON(inputs.cpu) }}
|
||||
exclude: ${{ fromJSON(inputs.exclude) }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.platform.shell }}
|
||||
|
||||
name: '${{ matrix.platform.os }}-${{ matrix.cpu }} (Nim ${{ matrix.branch }})'
|
||||
runs-on: ${{ matrix.platform.builder }}
|
||||
continue-on-error: ${{ matrix.branch == 'devel' || matrix.branch == 'version-2-0' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Nim
|
||||
uses: "./.github/actions/install_nim"
|
||||
with:
|
||||
os: ${{ matrix.platform.os }}
|
||||
shell: ${{ matrix.platform.shell }}
|
||||
nim_branch: ${{ matrix.branch }}
|
||||
cpu: ${{ matrix.cpu }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '~1.15.5'
|
||||
cache: false
|
||||
|
||||
- name: Install p2pd
|
||||
run: |
|
||||
V=1 bash scripts/build_p2pd.sh p2pdCache 124530a3
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
nim --version
|
||||
nimble --version
|
||||
nimble install -y --depsOnly
|
||||
NIMFLAGS="${NIMFLAGS} --mm:refc" nimble test
|
||||
if [[ "${{ matrix.branch }}" == "devel" ]]; then
|
||||
echo -e "\nTesting with '--mm:orc':\n"
|
||||
NIMFLAGS="${NIMFLAGS} --mm:orc" nimble test
|
||||
fi
|
||||
13
.github/workflows/daily_i386.yml
vendored
Normal file
13
.github/workflows/daily_i386.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Daily i386
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-multi-nim-common:
|
||||
uses: ./.github/workflows/daily_common.yml
|
||||
with:
|
||||
nim-branch: "['version-1-6','version-2-0', 'devel']"
|
||||
cpu: "['i386']"
|
||||
exclude: "[{'platform': {'os':'macos'}}, {'platform': {'os':'windows'}}]"
|
||||
12
.github/workflows/daily_nim_devel.yml
vendored
Normal file
12
.github/workflows/daily_nim_devel.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Daily Nim Devel
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-multi-nim-common:
|
||||
uses: ./.github/workflows/daily_common.yml
|
||||
with:
|
||||
nim-branch: "['devel']"
|
||||
cpu: "['amd64']"
|
||||
103
.github/workflows/doc.yml
vendored
Normal file
103
.github/workflows/doc.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
name: Docgen
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 20
|
||||
|
||||
name: 'Generate & upload documentation'
|
||||
runs-on: 'ubuntu-20.04'
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: jiro4989/setup-nim-action@v1
|
||||
with:
|
||||
nim-version: '1.6.x'
|
||||
|
||||
- name: Generate doc
|
||||
run: |
|
||||
nim --version
|
||||
nimble --version
|
||||
nimble install_pinned
|
||||
# nim doc can "fail", but the doc is still generated
|
||||
nim doc --git.url:https://github.com/status-im/nim-libp2p --git.commit:${GITHUB_REF##*/} --outdir:${GITHUB_REF##*/} --project libp2p || true
|
||||
|
||||
# check that the folder exists
|
||||
ls ${GITHUB_REF##*/}
|
||||
|
||||
- name: Clone the gh-pages branch
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: status-im/nim-libp2p
|
||||
ref: gh-pages
|
||||
path: subdoc
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Commit & push
|
||||
run: |
|
||||
cd subdoc
|
||||
|
||||
# Delete merged branches doc's
|
||||
for branch in $(git branch -vv | grep ': gone]' | awk '{print $1}'); do rm -rf $branch; done
|
||||
|
||||
# Update / create this branch doc
|
||||
rm -rf ${GITHUB_REF##*/}
|
||||
mv ../${GITHUB_REF##*/} .
|
||||
|
||||
# Remove .idx files
|
||||
# NOTE: git also uses idx files in his
|
||||
# internal folder, hence the `*` instead of `.`
|
||||
find * -name "*.idx" -delete
|
||||
git add .
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit -a -m "update docs for ${GITHUB_REF##*/}"
|
||||
git push origin gh-pages
|
||||
|
||||
update_site:
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/docs'
|
||||
name: 'Rebuild website'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: jiro4989/setup-nim-action@v1
|
||||
with:
|
||||
nim-version: 'stable'
|
||||
|
||||
- name: Generate website
|
||||
run: pip install mkdocs-material && nimble website
|
||||
|
||||
- name: Clone the gh-pages branch
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: status-im/nim-libp2p
|
||||
ref: gh-pages
|
||||
path: subdoc
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Commit & push
|
||||
run: |
|
||||
cd subdoc
|
||||
|
||||
rm -rf docs
|
||||
mv ../site docs
|
||||
|
||||
git add .
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
git config --global user.name = "${{ github.actor }}"
|
||||
git commit -a -m "update website"
|
||||
git push origin gh-pages
|
||||
40
.github/workflows/interop.yml
vendored
Normal file
40
.github/workflows/interop.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Interoperability Testing
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- unstable
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run-transport-interop:
|
||||
name: Run transport interoperability tests
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Build image
|
||||
run: docker buildx build --load -t nim-libp2p-head -f tests/transport-interop/Dockerfile .
|
||||
- name: Run tests
|
||||
uses: libp2p/test-plans/.github/actions/run-transport-interop-test@master
|
||||
with:
|
||||
test-filter: nim-libp2p-head
|
||||
extra-versions: ${{ github.workspace }}/tests/transport-interop/version.json
|
||||
|
||||
run-hole-punching-interop:
|
||||
name: Run hole-punching interoperability tests
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Build image
|
||||
run: docker buildx build --load -t nim-libp2p-head -f tests/hole-punching-interop/Dockerfile .
|
||||
- name: Run tests
|
||||
uses: libp2p/test-plans/.github/actions/run-interop-hole-punch-test@master
|
||||
with:
|
||||
test-filter: nim-libp2p-head
|
||||
extra-versions: ${{ github.workspace }}/tests/hole-punching-interop/version.json
|
||||
175
.github/workflows/multi_nim.yml
vendored
175
.github/workflows/multi_nim.yml
vendored
@@ -1,175 +0,0 @@
|
||||
name: Daily
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 120
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- os: linux
|
||||
cpu: amd64
|
||||
- os: linux
|
||||
cpu: i386
|
||||
- os: macos
|
||||
cpu: amd64
|
||||
- os: windows
|
||||
cpu: amd64
|
||||
#- os: windows
|
||||
#cpu: i386
|
||||
branch: [version-1-2, version-1-4, version-1-6, devel]
|
||||
include:
|
||||
- target:
|
||||
os: linux
|
||||
builder: ubuntu-20.04
|
||||
shell: bash
|
||||
- target:
|
||||
os: macos
|
||||
builder: macos-10.15
|
||||
shell: bash
|
||||
- target:
|
||||
os: windows
|
||||
builder: windows-2019
|
||||
shell: msys2 {0}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell }}
|
||||
|
||||
name: '${{ matrix.target.os }}-${{ matrix.target.cpu }} (Nim ${{ matrix.branch }})'
|
||||
runs-on: ${{ matrix.builder }}
|
||||
continue-on-error: ${{ matrix.branch == 'version-1-6' || matrix.branch == 'devel' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: unstable
|
||||
submodules: true
|
||||
|
||||
- name: Install build dependencies (Linux i386)
|
||||
if: runner.os == 'Linux' && matrix.target.cpu == 'i386'
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND='noninteractive' apt-get install \
|
||||
--no-install-recommends -yq gcc-multilib g++-multilib \
|
||||
libssl-dev:i386
|
||||
mkdir -p external/bin
|
||||
cat << EOF > external/bin/gcc
|
||||
#!/bin/bash
|
||||
exec $(which gcc) -m32 "\$@"
|
||||
EOF
|
||||
cat << EOF > external/bin/g++
|
||||
#!/bin/bash
|
||||
exec $(which g++) -m32 "\$@"
|
||||
EOF
|
||||
chmod 755 external/bin/gcc external/bin/g++
|
||||
echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: MSYS2 (Windows i386)
|
||||
if: runner.os == 'Windows' && matrix.target.cpu == 'i386'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
msystem: MINGW32
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-i686-toolchain
|
||||
|
||||
- name: MSYS2 (Windows amd64)
|
||||
if: runner.os == 'Windows' && matrix.target.cpu == 'amd64'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
path-type: inherit
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
mingw-w64-x86_64-toolchain
|
||||
|
||||
- name: Restore Nim DLLs dependencies (Windows) from cache
|
||||
if: runner.os == 'Windows'
|
||||
id: windows-dlls-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: external/dlls
|
||||
key: 'dlls'
|
||||
|
||||
- name: Install DLL dependencies (Windows)
|
||||
if: >
|
||||
steps.windows-dlls-cache.outputs.cache-hit != 'true' &&
|
||||
runner.os == 'Windows'
|
||||
run: |
|
||||
mkdir external
|
||||
curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip
|
||||
7z x external/windeps.zip -oexternal/dlls
|
||||
|
||||
- name: Path to cached dependencies (Windows)
|
||||
if: >
|
||||
runner.os == 'Windows'
|
||||
run: |
|
||||
echo '${{ github.workspace }}'"/external/dlls" >> $GITHUB_PATH
|
||||
|
||||
- name: Derive environment variables
|
||||
run: |
|
||||
if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then
|
||||
PLATFORM=x64
|
||||
else
|
||||
PLATFORM=x86
|
||||
fi
|
||||
echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
|
||||
|
||||
ncpu=
|
||||
MAKE_CMD="make"
|
||||
case '${{ runner.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
MAKE_CMD="mingw32-make"
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
echo "ncpu=$ncpu" >> $GITHUB_ENV
|
||||
echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Nim and Nimble
|
||||
run: |
|
||||
curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
|
||||
env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ matrix.branch }} \
|
||||
QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \
|
||||
bash build_nim.sh nim csources dist/nimble NimBinaries
|
||||
echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '~1.15.5'
|
||||
|
||||
- name: Install p2pd
|
||||
run: |
|
||||
V=1 bash scripts/build_p2pd.sh p2pdCache 124530a3
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [[ "${{ matrix.target.os }}" == "windows" ]]; then
|
||||
# https://github.com/status-im/nimbus-eth2/issues/3121
|
||||
export NIMFLAGS="-d:nimRawSetjmp"
|
||||
fi
|
||||
nim --version
|
||||
nimble --version
|
||||
nimble install -y --depsOnly
|
||||
nimble test
|
||||
if [[ "${{ matrix.branch }}" == "version-1-6" || "${{ matrix.branch }}" == "devel" ]]; then
|
||||
echo -e "\nTesting with '--gc:orc':\n"
|
||||
export NIMFLAGS="${NIMFLAGS} --gc:orc"
|
||||
nimble test
|
||||
fi
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -13,3 +13,7 @@ build/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
tests/pubsub/testgossipsub
|
||||
examples/*.md
|
||||
nimble.develop
|
||||
nimble.paths
|
||||
go-libp2p-daemon/
|
||||
|
||||
34
.pinned
34
.pinned
@@ -1,17 +1,17 @@
|
||||
asynctest;https://github.com/markspanbroek/asynctest@#5347c59b4b057443a014722aa40800cd8bb95c69
|
||||
bearssl;https://github.com/status-im/nim-bearssl@#0ebb1d7a4af5f4b4d4756a9b6dbfe5d411fa55d9
|
||||
chronicles;https://github.com/status-im/nim-chronicles@#2a2681b60289aaf7895b7056f22616081eb1a882
|
||||
chronos;https://github.com/status-im/nim-chronos@#875d7d8e6ef0803ae1c331dbf76b1981b0caeb15
|
||||
dnsclient;https://github.com/ba0f3/dnsclient.nim@#fbb76f8af8a33ab818184a7d4406d9fee20993be
|
||||
faststreams;https://github.com/status-im/nim-faststreams@#49e2c52eb5dda46b1c9c10d079abe7bffe6cea89
|
||||
httputils;https://github.com/status-im/nim-http-utils@#f83fbce4d6ec7927b75be3f85e4fa905fcb69788
|
||||
json_serialization;https://github.com/status-im/nim-json-serialization@#3509706517f3562cbcbe9d94988eccdd80474ab8
|
||||
metrics;https://github.com/status-im/nim-metrics@#11edec862f96e42374bc2d584c84cc88d5d1f95f
|
||||
nimcrypto;https://github.com/cheatfate/nimcrypto@#a5742a9a214ac33f91615f3862c7b099aec43b00
|
||||
secp256k1;https://github.com/status-im/nim-secp256k1@#e092373a5cbe1fa25abfc62e0f2a5f138dc3fb13
|
||||
serialization;https://github.com/status-im/nim-serialization@#9631fbd1c81c8b25ff8740df440ca7ba87fa6131
|
||||
stew;https://github.com/status-im/nim-stew@#cdb1f213d073fd2ecbdaf35a866417657da9294c
|
||||
testutils;https://github.com/status-im/nim-testutils@#aa6e5216f4b4ab5aa971cdcdd70e1ec1203cedf2
|
||||
unittest2;https://github.com/status-im/nim-unittest2@#4e2893eacb916c7678fdc4935ff7420f13bf3a9c
|
||||
websock;https://github.com/status-im/nim-websock@#8927db93f6ca96abaacfea39f8ca50ce9d41bcdb
|
||||
zlib;https://github.com/status-im/nim-zlib@#74cdeb54b21bededb5a515d36f608bc1850555a2
|
||||
bearssl;https://github.com/status-im/nim-bearssl@#e4157639db180e52727712a47deaefcbbac6ec86
|
||||
chronicles;https://github.com/status-im/nim-chronicles@#32ac8679680ea699f7dbc046e8e0131cac97d41a
|
||||
chronos;https://github.com/status-im/nim-chronos@#672db137b7cad9b384b8f4fb551fb6bbeaabfe1b
|
||||
dnsclient;https://github.com/ba0f3/dnsclient.nim@#23214235d4784d24aceed99bbfe153379ea557c8
|
||||
faststreams;https://github.com/status-im/nim-faststreams@#720fc5e5c8e428d9d0af618e1e27c44b42350309
|
||||
httputils;https://github.com/status-im/nim-http-utils@#3b491a40c60aad9e8d3407443f46f62511e63b18
|
||||
json_serialization;https://github.com/status-im/nim-json-serialization@#85b7ea093cb85ee4f433a617b97571bd709d30df
|
||||
metrics;https://github.com/status-im/nim-metrics@#6142e433fc8ea9b73379770a788017ac528d46ff
|
||||
nimcrypto;https://github.com/cheatfate/nimcrypto@#1c8d6e3caf3abc572136ae9a1da81730c4eb4288
|
||||
results;https://github.com/arnetheduck/nim-results@#f3c666a272c69d70cb41e7245e7f6844797303ad
|
||||
secp256k1;https://github.com/status-im/nim-secp256k1@#7246d91c667f4cc3759fdd50339caa45a2ecd8be
|
||||
serialization;https://github.com/status-im/nim-serialization@#4bdbc29e54fe54049950e352bb969aab97173b35
|
||||
stew;https://github.com/status-im/nim-stew@#3159137d9a3110edb4024145ce0ba778975de40e
|
||||
testutils;https://github.com/status-im/nim-testutils@#dfc4c1b39f9ded9baf6365014de2b4bfb4dafc34
|
||||
unittest2;https://github.com/status-im/nim-unittest2@#2300fa9924a76e6c96bc4ea79d043e3a0f27120c
|
||||
websock;https://github.com/status-im/nim-websock@#f8ed9b40a5ff27ad02a3c237c4905b0924e3f982
|
||||
zlib;https://github.com/status-im/nim-zlib@#38b72eda9d70067df4a953f56b5ed59630f2a17b
|
||||
244
README.md
244
README.md
@@ -1,13 +1,13 @@
|
||||
<h1 align="center">
|
||||
<a href="https://libp2p.io"><img width="250" src="https://github.com/libp2p/libp2p/blob/master/logo/black-bg-2.png?raw=true" alt="libp2p hex logo" /></a>
|
||||
<a href="https://libp2p.io"><img width="250" src="./.assets/full-logo.svg?raw=true" alt="nim-libp2p logo" /></a>
|
||||
</h1>
|
||||
|
||||
<h3 align="center">The Nim implementation of the libp2p Networking Stack.</h3>
|
||||
<h3 align="center">The <a href="https://nim-lang.org/">Nim</a> implementation of the <a href="https://libp2p.io/">libp2p</a> Networking Stack.</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/status-im/nim-libp2p/actions"><img src="https://github.com/status-im/nim-libp2p/actions/workflows/ci.yml/badge.svg" /></a>
|
||||
<a href="https://codecov.io/gh/status-im/nim-libp2p"><img src="https://codecov.io/gh/status-im/nim-libp2p/branch/master/graph/badge.svg?token=UR5JRQ249W"/></a>
|
||||
|
||||
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -16,169 +16,158 @@
|
||||
<img src="https://img.shields.io/badge/nim-%3E%3D1.2.0-orange.svg?style=flat-square" />
|
||||
</p>
|
||||
|
||||
## Introduction
|
||||
|
||||
An implementation of [libp2p](https://libp2p.io/) in Nim.
|
||||
|
||||
## Project Status
|
||||
libp2p is used in production by a few projects at [Status](https://github.com/status-im), including [Nimbus](https://github.com/status-im/nimbus-eth2).
|
||||
|
||||
While far from complete, currently available components are stable.
|
||||
|
||||
Check our [examples folder](/examples) to get started!
|
||||
|
||||
# Table of Contents
|
||||
- [Background](#background)
|
||||
- [Install](#install)
|
||||
- [Prerequisite](#prerequisite)
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Tutorials and Examples](#tutorials-and-examples)
|
||||
- [Using the Go Daemon](#using-the-go-daemon)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Go-libp2p-daemon](#go-libp2p-daemon)
|
||||
- [Modules](#modules)
|
||||
- [Users](#users)
|
||||
- [Stability](#stability)
|
||||
- [Development](#development)
|
||||
- [Tests](#tests)
|
||||
- [Packages](#packages)
|
||||
- [Contribute](#contribute)
|
||||
- [Core Developers](#core-developers)
|
||||
- [Contribute](#contribute)
|
||||
- [Contributors](#contributors)
|
||||
- [Core Maintainers](#core-maintainers)
|
||||
- [License](#license)
|
||||
|
||||
## Background
|
||||
libp2p is a networking stack and library modularized out of [The IPFS Project](https://github.com/ipfs/ipfs), and bundled separately for other tools to use.
|
||||
libp2p is a [Peer-to-Peer](https://en.wikipedia.org/wiki/Peer-to-peer) networking stack, with [implementations](https://github.com/libp2p/libp2p#implementations) in multiple languages derived from the same [specifications.](https://github.com/libp2p/specs)
|
||||
|
||||
libp2p is the product of a long and arduous quest of understanding; a deep dive into the internet's network stack and the peer-to-peer protocols from the past. Building large scale peer-to-peer systems has been complex and difficult in the last 15 years and libp2p is a way to fix that. It is a "network stack", a suite of networking protocols that cleanly separates concerns and enables sophisticated applications to only use the protocols they absolutely need, without giving up interoperability and upgradeability.
|
||||
Building large scale peer-to-peer systems has been complex and difficult in the last 15 years and libp2p is a way to fix that. It's striving to be a modular stack, with sane and secure defaults, useful protocols, while remain open and extensible.
|
||||
This implementation in native Nim, relying on [chronos](https://github.com/status-im/nim-chronos) for async. It's used in production by a few [projects](#users)
|
||||
|
||||
libp2p grew out of IPFS, but it is built so that lots of people can use it, for lots of different projects.
|
||||
|
||||
- Learn more about libp2p at [**libp2p.io**](https://libp2p.io) and follow our evolving documentation efforts at [**docs.libp2p.io**](https://docs.libp2p.io).
|
||||
- [Here](https://github.com/libp2p/libp2p#description) is an overview of libp2p and its implementations in other programming languages.
|
||||
Learn more about libp2p at [**libp2p.io**](https://libp2p.io) and follow libp2p's documentation [**docs.libp2p.io**](https://docs.libp2p.io).
|
||||
|
||||
## Install
|
||||
**Prerequisite**
|
||||
- [Nim](https://nim-lang.org/install.html)
|
||||
> The currently supported Nim version is 1.6.18.
|
||||
|
||||
```
|
||||
nimble install libp2p
|
||||
```
|
||||
### Prerequisite
|
||||
- [Nim](https://nim-lang.org/install.html)
|
||||
|
||||
## Usage
|
||||
## Getting Started
|
||||
You'll find the nim-libp2p documentation [here](https://status-im.github.io/nim-libp2p/docs/).
|
||||
|
||||
### API
|
||||
The specification is available in the [docs/api](docs/api) folder.
|
||||
### Testing
|
||||
Remember you'll need to build the `go-libp2p-daemon` binary to run the `nim-libp2p` tests.
|
||||
To do so, please follow the installation instructions in [daemonapi.md](examples/go-daemon/daemonapi.md).
|
||||
|
||||
### Getting Started
|
||||
Please read the [GETTING_STARTED.md](docs/GETTING_STARTED.md) guide.
|
||||
## Modules
|
||||
List of packages modules implemented in nim-libp2p:
|
||||
|
||||
### Tutorials and Examples
|
||||
Example code can be found in the [examples folder](/examples).
|
||||
| Name | Description |
|
||||
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| **Libp2p** | |
|
||||
| [libp2p](libp2p/switch.nim) | The core of the project |
|
||||
| [connmanager](libp2p/connmanager.nim) | Connection manager |
|
||||
| [identify / push identify](libp2p/protocols/identify.nim) | [Identify](https://docs.libp2p.io/concepts/fundamentals/protocols/#identify) protocol |
|
||||
| [ping](libp2p/protocols/ping.nim) | [Ping](https://docs.libp2p.io/concepts/fundamentals/protocols/#ping) protocol |
|
||||
| [libp2p-daemon-client](libp2p/daemon/daemonapi.nim) | [go-daemon](https://github.com/libp2p/go-libp2p-daemon) nim wrapper |
|
||||
| [interop-libp2p](tests/testinterop.nim) | Interop tests |
|
||||
| **Transports** | |
|
||||
| [libp2p-tcp](libp2p/transports/tcptransport.nim) | TCP transport |
|
||||
| [libp2p-ws](libp2p/transports/wstransport.nim) | WebSocket & WebSocket Secure transport |
|
||||
| [libp2p-tor](libp2p/transports/tortransport.nim) | Tor Transport |
|
||||
| **Secure Channels** | |
|
||||
| [libp2p-noise](libp2p/protocols/secure/noise.nim) | [Noise](https://docs.libp2p.io/concepts/secure-comm/noise/) secure channel |
|
||||
| [libp2p-plaintext](libp2p/protocols/secure/plaintext.nim) | Plain Text for development purposes |
|
||||
| **Stream Multiplexers** | |
|
||||
| [libp2p-mplex](libp2p/muxers/mplex/mplex.nim) | [MPlex](https://github.com/libp2p/specs/tree/master/mplex) multiplexer |
|
||||
| [libp2p-yamux](libp2p/muxers/yamux/yamux.nim) | [Yamux](https://docs.libp2p.io/concepts/multiplex/yamux/) multiplexer |
|
||||
| **Data Types** | |
|
||||
| [peer-id](libp2p/peerid.nim) | [Cryptographic identifiers](https://docs.libp2p.io/concepts/fundamentals/peers/#peer-id) |
|
||||
| [peer-store](libp2p/peerstore.nim) | ["Address book" of known peers](https://docs.libp2p.io/concepts/fundamentals/peers/#peer-store) |
|
||||
| [multiaddress](libp2p/multiaddress.nim) | [Composable network addresses](https://github.com/multiformats/multiaddr) |
|
||||
| [signed envelope](libp2p/signed_envelope.nim) | [Signed generic data container](https://github.com/libp2p/specs/blob/master/RFC/0002-signed-envelopes.md) |
|
||||
| [routing record](libp2p/routing_record.nim) | [Signed peer dialing informations](https://github.com/libp2p/specs/blob/master/RFC/0003-routing-records.md) |
|
||||
| [discovery manager](libp2p/discovery/discoverymngr.nim) | Discovery Manager |
|
||||
| **Utilities** | |
|
||||
| [libp2p-crypto](libp2p/crypto) | Cryptographic backend |
|
||||
| [libp2p-crypto-secp256k1](libp2p/crypto/secp.nim) | |
|
||||
| **Pubsub** | |
|
||||
| [libp2p-pubsub](libp2p/protocols/pubsub/pubsub.nim) | Pub-Sub generic interface |
|
||||
| [libp2p-floodsub](libp2p/protocols/pubsub/floodsub.nim) | FloodSub implementation |
|
||||
| [libp2p-gossipsub](libp2p/protocols/pubsub/gossipsub.nim) | [GossipSub](https://docs.libp2p.io/concepts/publish-subscribe/) implementation |
|
||||
|
||||
#### Direct Chat Tutorial
|
||||
- [Part I](https://our.status.im/nim-libp2p-tutorial-a-peer-to-peer-chat-example-1/): Set up the main function and use multi-thread for processing IO.
|
||||
- [Part II](https://our.status.im/nim-libp2p-tutorial-a-peer-to-peer-chat-example-2/): Dial remote peer and allow customized user input commands.
|
||||
- [Part III](https://our.status.im/nim-libp2p-tutorial-a-peer-to-peer-chat-example-3/): Configure and establish a libp2p node.
|
||||
## Users
|
||||
|
||||
nim-libp2p is used by:
|
||||
- [Nimbus](https://github.com/status-im/nimbus-eth2), an Ethereum client
|
||||
- [nwaku](https://github.com/status-im/nwaku), a decentralized messaging application
|
||||
- [nim-codex](https://github.com/status-im/nim-codex), a decentralized storage application
|
||||
- (open a pull request if you want to be included here)
|
||||
|
||||
### Using the Go Daemon
|
||||
Please find the installation and usage intructions in [daemonapi.md](docs/api/libp2p/daemonapi.md).
|
||||
## Stability
|
||||
nim-libp2p has been used in production for over a year in high-stake scenarios, so its core is considered stable.
|
||||
Some modules are more recent and less stable.
|
||||
|
||||
Examples can be found in the [examples/go-daemon folder](https://github.com/status-im/nim-libp2p/tree/readme/examples/go-daemon);
|
||||
The versioning follows [semver](https://semver.org/), with some additions:
|
||||
- Some of libp2p procedures are marked as `.public.`, they will remain compatible during each `MAJOR` version
|
||||
- The rest of the procedures are considered internal, and can change at any `MINOR` version (but remain compatible for each new `PATCH`)
|
||||
|
||||
We aim to be compatible at all time with at least 2 Nim `MINOR` versions, currently `1.6 & 2.0`
|
||||
|
||||
## Development
|
||||
**Clone and Install dependencies:**
|
||||
|
||||
Clone and Install dependencies:
|
||||
```sh
|
||||
git clone https://github.com/status-im/nim-libp2p
|
||||
cd nim-libp2p
|
||||
nimble install
|
||||
# to use dependencies computed by nimble
|
||||
nimble install -dy
|
||||
# OR to install the dependencies versions used in CI
|
||||
nimble install_pinned
|
||||
```
|
||||
#### Run unit tests
|
||||
|
||||
Run unit tests:
|
||||
```sh
|
||||
# run all the unit tests
|
||||
nimble test
|
||||
```
|
||||
The code follows the [Status Nim Style Guide](https://status-im.github.io/nim-style-guide/).
|
||||
This requires the go daemon to be available. To only run native tests, use `nimble testnative`.
|
||||
Or use `nimble tasks` to show all available tasks.
|
||||
|
||||
### Packages
|
||||
|
||||
List of packages currently in existence for nim-libp2p:
|
||||
|
||||
#### Libp2p
|
||||
- [libp2p](https://github.com/status-im/nim-libp2p)
|
||||
- [libp2p-daemon-client](https://github.com/status-im/nim-libp2p/blob/master/libp2p/daemon/daemonapi.nim)
|
||||
- [interop-libp2p](https://github.com/status-im/nim-libp2p/blob/master/tests/testinterop.nim)
|
||||
|
||||
#### Transports
|
||||
- [libp2p-tcp](https://github.com/status-im/nim-libp2p/blob/master/libp2p/transports/tcptransport.nim)
|
||||
- [libp2p-ws](https://github.com/status-im/nim-libp2p/blob/master/libp2p/transports/wstransport.nim)
|
||||
|
||||
#### Secure Channels
|
||||
- [libp2p-secio](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/secio.nim)
|
||||
- [libp2p-noise](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/noise.nim)
|
||||
- [libp2p-plaintext](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/plaintext.nim)
|
||||
|
||||
#### Stream Multiplexers
|
||||
- [libp2p-mplex](https://github.com/status-im/nim-libp2p/blob/master/libp2p/muxers/mplex/mplex.nim)
|
||||
|
||||
#### Utilities
|
||||
- [libp2p-crypto](https://github.com/status-im/nim-libp2p/tree/master/libp2p/crypto)
|
||||
- [libp2p-crypto-secp256k1](https://github.com/status-im/nim-libp2p/blob/master/libp2p/crypto/secp.nim)
|
||||
|
||||
#### Data Types
|
||||
- [peer-id](https://github.com/status-im/nim-libp2p/blob/master/libp2p/peer.nim)
|
||||
- [peer-info](https://github.com/status-im/nim-libp2p/blob/master/libp2p/peerinfo.nim)
|
||||
|
||||
#### Pubsub
|
||||
- [libp2p-pubsub](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/pubsub/pubsub.nim)
|
||||
- [libp2p-floodsub](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/pubsub/floodsub.nim)
|
||||
- [libp2p-gossipsub](https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/pubsub/gossipsub.nim)
|
||||
|
||||
|
||||
Packages that exist in the original libp2p specs and are under active development:
|
||||
- libp2p-daemon
|
||||
- libp2p-webrtc-direct
|
||||
- libp2p-webrtc-star
|
||||
- libp2p-spdy
|
||||
- libp2p-bootstrap
|
||||
- libp2p-kad-dht
|
||||
- libp2p-mdns
|
||||
- libp2p-webrtc-star
|
||||
- libp2p-delegated-content-routing
|
||||
- libp2p-delegated-peer-routing
|
||||
- libp2p-nat-mgnr
|
||||
- libp2p-utils
|
||||
|
||||
** Note that the current stack reflects the minimal requirements for the upcoming Eth2 implementation.
|
||||
|
||||
### Tips and tricks
|
||||
|
||||
#### enable expensive metrics:
|
||||
|
||||
```bash
|
||||
nim c -d:libp2p_expensive_metrics some_file.nim
|
||||
```
|
||||
|
||||
#### use identify metrics
|
||||
|
||||
```bash
|
||||
nim c -d:libp2p_agents_metrics -d:KnownLibP2PAgents=nimbus,lighthouse,prysm,teku some_file.nim
|
||||
```
|
||||
|
||||
### specify gossipsub specific topics to measure
|
||||
|
||||
```bash
|
||||
nim c -d:KnownLibP2PTopics=topic1,topic2,topic3 some_file.nim
|
||||
```
|
||||
|
||||
## Contribute
|
||||
### Contribute
|
||||
|
||||
The libp2p implementation in Nim is a work in progress. We welcome contributors to help out! Specifically, you can:
|
||||
- Go through the modules and **check out existing issues**. This would be especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it.
|
||||
- **Perform code reviews**. Feel free to let us know if you found anything that can a) speed up the project development b) ensure better quality and c) reduce possible future bugs.
|
||||
- **Add tests**. Help nim-libp2p to be more robust by adding more tests to the [tests folder](https://github.com/status-im/nim-libp2p/tree/master/tests).
|
||||
|
||||
- **Add tests**. Help nim-libp2p to be more robust by adding more tests to the [tests folder](tests/).
|
||||
- **Small PRs**. Try to keep PRs atomic and digestible. This makes the review process and pinpointing bugs easier.
|
||||
- **Code format**. Please format code using [nph](https://github.com/arnetheduck/nph).
|
||||
The code follows the [Status Nim Style Guide](https://status-im.github.io/nim-style-guide/).
|
||||
|
||||
### Core Developers
|
||||
[@cheatfate](https://github.com/cheatfate), [Dmitriy Ryajov](https://github.com/dryajov), [Tanguy](https://github.com/Menduist), [Zahary Karadjov](https://github.com/zah)
|
||||
### Contributors
|
||||
<a href="https://github.com/status-im/nim-libp2p/graphs/contributors"><img src="https://contrib.rocks/image?repo=status-im/nim-libp2p" alt="nim-libp2p contributors"></a>
|
||||
|
||||
### Core Maintainers
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Menduist"><img src="https://avatars.githubusercontent.com/u/13471753?v=4?s=100" width="100px;" alt="Tanguy"/><br /><sub><b>Tanguy (Menduist)</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/lchenut"><img src="https://avatars.githubusercontent.com/u/11214565?v=4?s=100" width="100px;" alt="Ludovic"/><br /><sub><b>Ludovic</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/diegomrsantos"><img src="https://avatars.githubusercontent.com/u/7316595?v=4?s=100" width="100px;" alt="Diego"/><br /><sub><b>Diego</b></sub></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Compile time flags
|
||||
|
||||
Enable expensive metrics (ie, metrics with per-peer cardinality):
|
||||
```bash
|
||||
nim c -d:libp2p_expensive_metrics some_file.nim
|
||||
```
|
||||
|
||||
Set list of known libp2p agents for metrics:
|
||||
```bash
|
||||
nim c -d:libp2p_agents_metrics -d:KnownLibP2PAgents=nimbus,lighthouse,lodestar,prysm,teku some_file.nim
|
||||
```
|
||||
|
||||
Specify gossipsub specific topics to measure in the metrics:
|
||||
```bash
|
||||
nim c -d:KnownLibP2PTopics=topic1,topic2,topic3 some_file.nim
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
@@ -191,4 +180,3 @@ or
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
at your option. These files may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
codecov:
|
||||
notify:
|
||||
require_ci_to_pass: true
|
||||
# must be the number of coverage report builds
|
||||
# notice that this number is for PRs;
|
||||
# like this we disabled notify on pure branches report
|
||||
# which is fine I guess
|
||||
after_n_builds: 28
|
||||
comment:
|
||||
layout: "reach, diff, flags, files"
|
||||
after_n_builds: 28 # must be the number of coverage report builds
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
@@ -16,4 +10,4 @@ coverage:
|
||||
# basic settings
|
||||
target: auto
|
||||
threshold: 5%
|
||||
base: auto
|
||||
base: auto
|
||||
|
||||
20
config.nims
20
config.nims
@@ -1,6 +1,22 @@
|
||||
# to allow locking
|
||||
if dirExists("nimbledeps/pkgs"):
|
||||
switch("NimblePath", "nimbledeps/pkgs")
|
||||
if dirExists("nimbledeps/pkgs2"):
|
||||
switch("NimblePath", "nimbledeps/pkgs2")
|
||||
|
||||
when (NimMajor, NimMinor) > (1, 2):
|
||||
switch("hint", "XCannotRaiseY:off")
|
||||
switch("warning", "CaseTransition:off")
|
||||
switch("warning", "ObservableStores:off")
|
||||
switch("warning", "LockLevel:off")
|
||||
--styleCheck:usages
|
||||
switch("warningAsError", "UseBase:on")
|
||||
--styleCheck:error
|
||||
|
||||
# Avoid some rare stack corruption while using exceptions with a SEH-enabled
|
||||
# toolchain: https://github.com/status-im/nimbus-eth2/issues/3121
|
||||
if defined(windows) and not defined(vcc):
|
||||
--define:nimRawSetjmp
|
||||
|
||||
# begin Nimble config (version 1)
|
||||
when fileExists("nimble.paths"):
|
||||
include "nimble.paths"
|
||||
# end Nimble config
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# API
|
||||
|
||||
Coming Soon...
|
||||
@@ -1,7 +0,0 @@
|
||||
# Getting Started
|
||||
Welcome to nim-libp2p!
|
||||
|
||||
|
||||
To get started, please look at the [tutorials](../examples/tutorial_1_connect.md)
|
||||
|
||||
For more concrete examples, you can look at the [hello world example](../examples/helloworld.nim) or the [direct chat](../examples/directchat.nim)
|
||||
@@ -1,29 +0,0 @@
|
||||
# Introduction
|
||||
This folder contains the documentation for each nim-libp2p module and the sample code for the tutorials.
|
||||
|
||||
# Table of Contents
|
||||
### [Getting Started](GETTING_STARTED.md)
|
||||
### Tutorials
|
||||
- P2P Chat Example
|
||||
- [part I](tutorial/directchat/start.nim)
|
||||
- [part II](tutorial/directchat/second.nim)
|
||||
### API Specifications
|
||||
- libp2p
|
||||
- [libp2p-daemon-client](api/libp2p/daemonapi.md)
|
||||
- [interop-libp2p](api/libp2p/interop.md)
|
||||
- transports
|
||||
- [libp2p-tcp](api/transports/tcptransport.md)
|
||||
- secure channels
|
||||
- [libp2p-secio](api/secure_channels/secio.md)
|
||||
- stream multiplexers
|
||||
- [libp2p-mplex](api/stream_multiplexers/mplex.md)
|
||||
- utilities
|
||||
- [libp2p-crypto](api/utilities/crypto.md)
|
||||
- [libp2p-crypto-secp256k1](api/utilities/secp256k1.md)
|
||||
- data types
|
||||
- [peer-id](api/data_types/peer.md)
|
||||
- [peer-info](api/data_types/peerinfo.md)
|
||||
- pubsub
|
||||
- [libp2p-pubsub](api/pubsub/pubsub.md)
|
||||
- [libp2p-floodsub](api/pubsub/floodsub.md)
|
||||
- [libp2p-gossipsub](api/pubsub/gossipsub.md)
|
||||
@@ -1,149 +0,0 @@
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import tables, strformat, strutils
|
||||
import chronos
|
||||
import ../libp2p/[switch,
|
||||
multistream,
|
||||
crypto/crypto,
|
||||
protocols/identify,
|
||||
connection,
|
||||
transports/transport,
|
||||
transports/tcptransport,
|
||||
multiaddress,
|
||||
peerinfo,
|
||||
peerid,
|
||||
protocols/protocol,
|
||||
protocols/secure/secure,
|
||||
protocols/secure/secio,
|
||||
muxers/muxer,
|
||||
muxers/mplex/mplex]
|
||||
|
||||
const ChatCodec = "/nim-libp2p/chat/1.0.0"
|
||||
const DefaultAddr = "/ip4/127.0.0.1/tcp/55505"
|
||||
|
||||
const Help = """
|
||||
Commands: /[?|hep|connect|disconnect|exit]
|
||||
help: Prints this help
|
||||
connect: dials a remote peer
|
||||
disconnect: ends current session
|
||||
exit: closes the chat
|
||||
"""
|
||||
|
||||
type ChatProto = ref object of LPProtocol
|
||||
switch: Switch # a single entry point for dialing and listening to peer
|
||||
transp: StreamTransport # transport streams between read & write file descriptor
|
||||
conn: Connection # create and close read & write stream
|
||||
connected: bool # if the node is connected to another peer
|
||||
started: bool # if the node has started
|
||||
|
||||
# copied from https://github.com/status-im/nimbus-eth2/blob/0ed657e953740a92458f23033d47483ffa17ccb0/beacon_chain/eth2_network.nim#L109-L115
|
||||
proc initAddress(T: type MultiAddress, str: string): T =
|
||||
let address = MultiAddress.init(str)
|
||||
if IPFS.match(address) and matchPartial(multiaddress.TCP, address):
|
||||
result = address
|
||||
else:
|
||||
raise newException(MultiAddressError,
|
||||
"Invalid bootstrap node multi-address")
|
||||
|
||||
proc dialPeer(p: ChatProto, address: string) {.async.} =
|
||||
let multiAddr = MultiAddress.initAddress(address);
|
||||
let parts = address.split("/")
|
||||
let remotePeer = PeerInfo.init(parts[^1],
|
||||
[multiAddr])
|
||||
|
||||
echo &"dialing peer: {multiAddr}"
|
||||
p.conn = await p.switch.dial(remotePeer, ChatCodec)
|
||||
p.connected = true
|
||||
|
||||
proc readAndPrint(p: ChatProto) {.async.} =
|
||||
while true:
|
||||
while p.connected:
|
||||
echo cast[string](await p.conn.readLp(1024))
|
||||
await sleepAsync(100.millis)
|
||||
|
||||
proc writeAndPrint(p: ChatProto) {.async.} =
|
||||
while true:
|
||||
if not p.connected:
|
||||
echo "type an address or wait for a connection:"
|
||||
echo "type /[help|?] for help"
|
||||
|
||||
let line = await p.transp.readLine()
|
||||
if line.startsWith("/help") or line.startsWith("/?") or not p.started:
|
||||
echo Help
|
||||
continue
|
||||
|
||||
if line.startsWith("/disconnect"):
|
||||
echo "Ending current session"
|
||||
if p.connected and p.conn.closed.not:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
elif line.startsWith("/connect"):
|
||||
if p.connected:
|
||||
var yesno = "N"
|
||||
echo "a session is already in progress, do you want end it [y/N]?"
|
||||
yesno = await p.transp.readLine()
|
||||
if yesno.cmpIgnoreCase("y") == 0:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
elif yesno.cmpIgnoreCase("n") == 0:
|
||||
continue
|
||||
else:
|
||||
echo "unrecognized response"
|
||||
continue
|
||||
|
||||
echo "enter address of remote peer"
|
||||
let address = await p.transp.readLine()
|
||||
if address.len > 0:
|
||||
await p.dialPeer(address)
|
||||
|
||||
elif line.startsWith("/exit"):
|
||||
if p.connected and p.conn.closed.not:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
|
||||
await p.switch.stop()
|
||||
echo "quitting..."
|
||||
quit(0)
|
||||
else:
|
||||
if p.connected:
|
||||
await p.conn.writeLp(line)
|
||||
else:
|
||||
try:
|
||||
if line.startsWith("/") and "ipfs" in line:
|
||||
await p.dialPeer(line)
|
||||
except:
|
||||
echo &"unable to dial remote peer {line}"
|
||||
echo getCurrentExceptionMsg()
|
||||
|
||||
proc readWriteLoop(p: ChatProto) {.async.} =
|
||||
asyncSpawn p.writeAndPrint() # execute the async function but does not block
|
||||
asyncSpawn p.readAndPrint()
|
||||
|
||||
proc processInput(rfd: AsyncFD) {.async.} =
|
||||
let transp = fromPipe(rfd)
|
||||
while true:
|
||||
let a = await transp.readLine()
|
||||
echo "You just entered: " & a
|
||||
|
||||
proc readInput(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
## pipe to main thread.
|
||||
let transp = fromPipe(wfd)
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
discard waitFor transp.write(line & "\r\n")
|
||||
|
||||
proc main() {.async.} =
|
||||
let (rfd, wfd) = createAsyncPipe()
|
||||
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
||||
raise newException(ValueError, "Could not initialize pipe!")
|
||||
|
||||
var thread: Thread[AsyncFD]
|
||||
thread.createThread(readInput, wfd)
|
||||
|
||||
await processInput(rfd)
|
||||
|
||||
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
||||
waitFor(main())
|
||||
@@ -1,39 +0,0 @@
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import chronos # an efficient library for async
|
||||
|
||||
proc processInput(rfd: AsyncFD) {.async.} =
|
||||
echo "Type something below to see if the multithread IO works:\nType 'exit' to exit."
|
||||
|
||||
let transp = fromPipe(rfd)
|
||||
while true:
|
||||
let a = await transp.readLine()
|
||||
|
||||
if a == "exit":
|
||||
quit(0);
|
||||
|
||||
echo "You just entered: " & a
|
||||
|
||||
proc readInput(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
## pipe to main thread.
|
||||
let transp = fromPipe(wfd)
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
discard waitFor transp.write(line & "\r\n")
|
||||
|
||||
proc main() {.async.} =
|
||||
let (rfd, wfd) = createAsyncPipe()
|
||||
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
||||
raise newException(ValueError, "Could not initialize pipe!")
|
||||
|
||||
var thread: Thread[AsyncFD]
|
||||
thread.createThread(readInput, wfd)
|
||||
|
||||
await processInput(rfd)
|
||||
|
||||
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
||||
waitFor(main())
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import tables, strformat, strutils, bearssl
|
||||
import chronos # an efficient library for async
|
||||
import ../libp2p/[switch, # manage transports, a single entry point for dialing and listening
|
||||
builders, # helper to build the switch object
|
||||
multistream, # tag stream with short header to identify it
|
||||
multicodec, # multicodec utilities
|
||||
crypto/crypto, # cryptographic functions
|
||||
errors, # error handling utilities
|
||||
protocols/identify, # identify the peer info of a peer
|
||||
stream/connection, # create and close stream read / write connections
|
||||
transports/transport, # listen and dial to other peers using p2p protocol
|
||||
transports/tcptransport, # listen and dial to other peers using client-server protocol
|
||||
multiaddress, # encode different addressing schemes. For example, /ip4/7.7.7.7/tcp/6543 means it is using IPv4 protocol and TCP
|
||||
peerinfo, # manage the information of a peer, such as peer ID and public / private key
|
||||
peerid, # Implement how peers interact
|
||||
protocols/protocol, # define the protocol base type
|
||||
protocols/secure/secure, # define the protocol of secure connection
|
||||
protocols/secure/secio, # define the protocol of secure input / output, allows encrypted communication that uses public keys to validate signed messages instead of a certificate authority like in TLS
|
||||
muxers/muxer, # define an interface for stream multiplexing, allowing peers to offer many protocols over a single connection
|
||||
muxers/mplex/mplex] # define some contants and message types for stream multiplexing
|
||||
|
||||
const ChatCodec = "/nim-libp2p/chat/1.0.0"
|
||||
const DefaultAddr = "/ip4/127.0.0.1/tcp/55505"
|
||||
|
||||
const Help = """
|
||||
Commands: /[?|hep|connect|disconnect|exit]
|
||||
help: Prints this help
|
||||
connect: dials a remote peer
|
||||
disconnect: ends current session
|
||||
exit: closes the chat
|
||||
"""
|
||||
|
||||
type ChatProto = ref object of LPProtocol
|
||||
switch: Switch # a single entry point for dialing and listening to peer
|
||||
transp: StreamTransport # transport streams between read & write file descriptor
|
||||
conn: Connection # create and close read & write stream
|
||||
connected: bool # if the node is connected to another peer
|
||||
started: bool # if the node has started
|
||||
|
||||
proc readAndPrint(p: ChatProto) {.async.} =
|
||||
while true:
|
||||
var strData = await p.conn.readLp(1024)
|
||||
strData &= '\0'.uint8
|
||||
var str = cast[cstring](addr strdata[0])
|
||||
echo $p.switch.peerInfo.peerId & ": " & $str
|
||||
await sleepAsync(100.millis)
|
||||
|
||||
proc dialPeer(p: ChatProto, address: string) {.async.} =
|
||||
let
|
||||
multiAddr = MultiAddress.init(address).tryGet()
|
||||
# split the peerId part /p2p/...
|
||||
peerIdBytes = multiAddr[multiCodec("p2p")]
|
||||
.tryGet()
|
||||
.protoAddress()
|
||||
.tryGet()
|
||||
remotePeer = PeerId.init(peerIdBytes).tryGet()
|
||||
# split the wire address
|
||||
ip4Addr = multiAddr[multiCodec("ip4")].tryGet()
|
||||
tcpAddr = multiAddr[multiCodec("tcp")].tryGet()
|
||||
wireAddr = ip4Addr & tcpAddr
|
||||
|
||||
echo &"dialing peer: {multiAddr}"
|
||||
p.conn = await p.switch.dial(remotePeer, @[wireAddr], ChatCodec)
|
||||
p.connected = true
|
||||
asyncSpawn p.readAndPrint()
|
||||
|
||||
proc writeAndPrint(p: ChatProto) {.async.} =
|
||||
while true:
|
||||
if not p.connected:
|
||||
echo "type an address or wait for a connection:"
|
||||
echo "type /[help|?] for help"
|
||||
|
||||
let line = await p.transp.readLine()
|
||||
if line.startsWith("/help") or line.startsWith("/?") or not p.started:
|
||||
echo Help
|
||||
continue
|
||||
|
||||
if line.startsWith("/disconnect"):
|
||||
echo "Ending current session"
|
||||
if p.connected and p.conn.closed.not:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
elif line.startsWith("/connect"):
|
||||
if p.connected:
|
||||
var yesno = "N"
|
||||
echo "a session is already in progress, do you want end it [y/N]?"
|
||||
yesno = await p.transp.readLine()
|
||||
if yesno.cmpIgnoreCase("y") == 0:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
elif yesno.cmpIgnoreCase("n") == 0:
|
||||
continue
|
||||
else:
|
||||
echo "unrecognized response"
|
||||
continue
|
||||
|
||||
echo "enter address of remote peer"
|
||||
let address = await p.transp.readLine()
|
||||
if address.len > 0:
|
||||
await p.dialPeer(address)
|
||||
|
||||
elif line.startsWith("/exit"):
|
||||
if p.connected and p.conn.closed.not:
|
||||
await p.conn.close()
|
||||
p.connected = false
|
||||
|
||||
await p.switch.stop()
|
||||
echo "quitting..."
|
||||
quit(0)
|
||||
else:
|
||||
if p.connected:
|
||||
await p.conn.writeLp(line)
|
||||
else:
|
||||
try:
|
||||
if line.startsWith("/") and "p2p" in line:
|
||||
await p.dialPeer(line)
|
||||
except:
|
||||
echo &"unable to dial remote peer {line}"
|
||||
echo getCurrentExceptionMsg()
|
||||
|
||||
proc readWriteLoop(p: ChatProto) {.async.} =
|
||||
await p.writeAndPrint()
|
||||
|
||||
proc newChatProto(switch: Switch, transp: StreamTransport): ChatProto =
|
||||
var chatproto = ChatProto(switch: switch, transp: transp, codecs: @[ChatCodec])
|
||||
|
||||
# create handler for incoming connection
|
||||
proc handle(stream: Connection, proto: string) {.async.} =
|
||||
if chatproto.connected and not chatproto.conn.closed:
|
||||
echo "a chat session is already in progress - disconnecting!"
|
||||
await stream.close()
|
||||
else:
|
||||
chatproto.conn = stream
|
||||
chatproto.connected = true
|
||||
await chatproto.readAndPrint()
|
||||
|
||||
# assign the new handler
|
||||
chatproto.handler = handle
|
||||
return chatproto
|
||||
|
||||
proc readInput(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
## pipe to main thread.
|
||||
let transp = fromPipe(wfd)
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
discard waitFor transp.write(line & "\r\n")
|
||||
|
||||
proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} =
|
||||
let transp = fromPipe(rfd)
|
||||
|
||||
let seckey = PrivateKey.random(RSA, rng[]).get()
|
||||
var localAddress = DefaultAddr
|
||||
while true:
|
||||
echo &"Type an address to bind to or Enter to use the default {DefaultAddr}"
|
||||
let a = await transp.readLine()
|
||||
try:
|
||||
if a.len > 0:
|
||||
localAddress = a
|
||||
break
|
||||
# uise default
|
||||
break
|
||||
except:
|
||||
echo "invalid address"
|
||||
localAddress = DefaultAddr
|
||||
continue
|
||||
|
||||
var switch = SwitchBuilder
|
||||
.init()
|
||||
.withRng(rng)
|
||||
.withPrivateKey(seckey)
|
||||
.withAddress(MultiAddress.init(localAddress).tryGet())
|
||||
.build()
|
||||
|
||||
let chatProto = newChatProto(switch, transp)
|
||||
switch.mount(chatProto)
|
||||
let libp2pFuts = await switch.start()
|
||||
chatProto.started = true
|
||||
|
||||
let id = $switch.peerInfo.peerId
|
||||
echo "PeerId: " & id
|
||||
echo "listening on: "
|
||||
for a in switch.peerInfo.addrs:
|
||||
echo &"{a}/p2p/{id}"
|
||||
|
||||
await chatProto.readWriteLoop()
|
||||
await allFuturesThrowing(libp2pFuts)
|
||||
|
||||
proc main() {.async.} =
|
||||
let rng = newRng() # Singe random number source for the whole application
|
||||
let (rfd, wfd) = createAsyncPipe()
|
||||
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
||||
raise newException(ValueError, "Could not initialize pipe!")
|
||||
|
||||
var thread: Thread[AsyncFD]
|
||||
thread.createThread(readInput, wfd)
|
||||
|
||||
await processInput(rfd, rng)
|
||||
|
||||
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
||||
waitFor(main())
|
||||
@@ -1,39 +0,0 @@
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import chronos # an efficient library for async
|
||||
|
||||
proc processInput(rfd: AsyncFD) {.async.} =
|
||||
echo "Type something below to see if the multithread IO works:\nType 'exit' to exit."
|
||||
|
||||
let transp = fromPipe(rfd)
|
||||
while true:
|
||||
let a = await transp.readLine()
|
||||
|
||||
if a == "exit":
|
||||
quit(0);
|
||||
|
||||
echo "You just entered: " & a
|
||||
|
||||
proc readInput(wfd: AsyncFD) {.thread.} =
|
||||
## This procedure performs reading from `stdin` and sends data over
|
||||
## pipe to main thread.
|
||||
let transp = fromPipe(wfd)
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
discard waitFor transp.write(line & "\r\n")
|
||||
|
||||
proc main() {.async.} =
|
||||
let (rfd, wfd) = createAsyncPipe()
|
||||
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
||||
raise newException(ValueError, "Could not initialize pipe!")
|
||||
|
||||
var thread: Thread[AsyncFD]
|
||||
thread.createThread(readInput, wfd)
|
||||
|
||||
await processInput(rfd)
|
||||
|
||||
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
||||
waitFor(main())
|
||||
|
||||
5
examples/README.md
Normal file
5
examples/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# nim-libp2p examples
|
||||
|
||||
In this folder, you'll find the sources of the [nim-libp2p website](https://status-im.github.io/nim-libp2p/docs/)
|
||||
|
||||
We recommand to follow the tutorials on the website, but feel free to grok the sources here!
|
||||
83
examples/circuitrelay.nim
Normal file
83
examples/circuitrelay.nim
Normal file
@@ -0,0 +1,83 @@
|
||||
## # Circuit Relay example
|
||||
##
|
||||
## Circuit Relay can be used when a node cannot reach another node
|
||||
## directly, but can reach it through a another node (the Relay).
|
||||
##
|
||||
## That may happen because of NAT, Firewalls, or incompatible transports.
|
||||
##
|
||||
## More informations [here](https://docs.libp2p.io/concepts/circuit-relay/).
|
||||
import chronos, stew/byteutils
|
||||
import libp2p,
|
||||
libp2p/protocols/connectivity/relay/[relay, client]
|
||||
|
||||
# Helper to create a circuit relay node
|
||||
proc createCircuitRelaySwitch(r: Relay): Switch =
|
||||
SwitchBuilder.new()
|
||||
.withRng(newRng())
|
||||
.withAddresses(@[ MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet() ])
|
||||
.withTcpTransport()
|
||||
.withMplex()
|
||||
.withNoise()
|
||||
.withCircuitRelay(r)
|
||||
.build()
|
||||
|
||||
proc main() {.async.} =
|
||||
# Create a custom protocol
|
||||
let customProtoCodec = "/test"
|
||||
var proto = new LPProtocol
|
||||
proto.codec = customProtoCodec
|
||||
proto.handler = proc(conn: Connection, proto: string) {.async.} =
|
||||
var msg = string.fromBytes(await conn.readLp(1024))
|
||||
echo "1 - Dst Received: ", msg
|
||||
assert "test1" == msg
|
||||
await conn.writeLp("test2")
|
||||
msg = string.fromBytes(await conn.readLp(1024))
|
||||
echo "2 - Dst Received: ", msg
|
||||
assert "test3" == msg
|
||||
await conn.writeLp("test4")
|
||||
|
||||
let
|
||||
relay = Relay.new()
|
||||
clSrc = RelayClient.new()
|
||||
clDst = RelayClient.new()
|
||||
|
||||
# Create three hosts, enable relay client on two of them.
|
||||
# The third one can relay connections for other peers.
|
||||
# RelayClient can use a relay, Relay is a relay.
|
||||
swRel = createCircuitRelaySwitch(relay)
|
||||
swSrc = createCircuitRelaySwitch(clSrc)
|
||||
swDst = createCircuitRelaySwitch(clDst)
|
||||
|
||||
swDst.mount(proto)
|
||||
|
||||
await swRel.start()
|
||||
await swSrc.start()
|
||||
await swDst.start()
|
||||
|
||||
let
|
||||
# Create a relay address to swDst using swRel as the relay
|
||||
addrs = MultiAddress.init($swRel.peerInfo.addrs[0] & "/p2p/" &
|
||||
$swRel.peerInfo.peerId & "/p2p-circuit").get()
|
||||
|
||||
# Connect Dst to the relay
|
||||
await swDst.connect(swRel.peerInfo.peerId, swRel.peerInfo.addrs)
|
||||
|
||||
# Dst reserve a slot on the relay.
|
||||
let rsvp = await clDst.reserve(swRel.peerInfo.peerId, swRel.peerInfo.addrs)
|
||||
|
||||
# Src dial Dst using the relay
|
||||
let conn = await swSrc.dial(swDst.peerInfo.peerId, @[ addrs ], customProtoCodec)
|
||||
|
||||
await conn.writeLp("test1")
|
||||
var msg = string.fromBytes(await conn.readLp(1024))
|
||||
echo "1 - Src Received: ", msg
|
||||
assert "test2" == msg
|
||||
await conn.writeLp("test3")
|
||||
msg = string.fromBytes(await conn.readLp(1024))
|
||||
echo "2 - Src Received: ", msg
|
||||
assert "test4" == msg
|
||||
|
||||
await relay.stop()
|
||||
await allFutures(swSrc.stop(), swDst.stop(), swRel.stop())
|
||||
|
||||
waitFor(main())
|
||||
@@ -2,10 +2,10 @@ when not(compileOption("threads")):
|
||||
{.fatal: "Please, compile this program with the --threads:on option!".}
|
||||
|
||||
import
|
||||
strformat, strutils, bearssl,
|
||||
strformat, strutils,
|
||||
stew/byteutils,
|
||||
chronos,
|
||||
../libp2p
|
||||
libp2p
|
||||
|
||||
const DefaultAddr = "/ip4/127.0.0.1/tcp/0"
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Table of Contents
|
||||
- [Introduction](#introduction)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Script](#script)
|
||||
- [Usage](#usage)
|
||||
- [Example](#example)
|
||||
- [Getting Started](#getting-started)
|
||||
@@ -8,26 +10,29 @@
|
||||
# Introduction
|
||||
This is a libp2p-backed daemon wrapping the functionalities of go-libp2p for use in Nim. <br>
|
||||
For more information about the go daemon, check out [this repository](https://github.com/libp2p/go-libp2p-daemon).
|
||||
> **Required only** for running the tests.
|
||||
|
||||
# Prerequisites
|
||||
Go with version `1.15.15`.
|
||||
> You will *likely* be able to build `go-libp2p-daemon` with different Go versions, but **they haven't been tested**.
|
||||
|
||||
# Installation
|
||||
Follow one of the methods below:
|
||||
|
||||
## Script
|
||||
Run the build script while having the `go` command pointing to the correct Go version.
|
||||
We recommend using `1.15.15`, as previously stated.
|
||||
```sh
|
||||
# clone and install dependencies
|
||||
git clone https://github.com/status-im/nim-libp2p
|
||||
cd nim-libp2p
|
||||
nimble install
|
||||
|
||||
# perform unit tests
|
||||
nimble test
|
||||
|
||||
# update the git submodule to install the go daemon
|
||||
git submodule update --init --recursive
|
||||
go version
|
||||
git clone https://github.com/libp2p/go-libp2p-daemon
|
||||
cd go-libp2p-daemon
|
||||
git checkout v0.0.1
|
||||
go install ./...
|
||||
cd ..
|
||||
./scripts/build_p2pd.sh
|
||||
```
|
||||
If everything goes correctly, the binary (`p2pd`) should be built and placed in the correct directory.
|
||||
If you find any issues, please head into our discord and ask for our asistance.
|
||||
|
||||
After successfully building the binary, remember to add it to your path so it can be found. You can do that by running:
|
||||
```sh
|
||||
export PATH="$PATH:$HOME/go/bin"
|
||||
```
|
||||
> **Tip:** To make this change permanent, add the command above to your `.bashrc` file.
|
||||
|
||||
# Usage
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import bearssl
|
||||
import chronos # an efficient library for async
|
||||
import stew/byteutils # various utils
|
||||
import ../libp2p # when installed through nimble, just use `import libp2p`
|
||||
import libp2p
|
||||
|
||||
##
|
||||
# Create our custom protocol
|
||||
@@ -14,19 +13,19 @@ type
|
||||
proc new(T: typedesc[TestProto]): T =
|
||||
|
||||
# every incoming connections will be in handled in this closure
|
||||
proc handle(conn: Connection, proto: string) {.async, gcsafe.} =
|
||||
proc handle(conn: Connection, proto: string) {.async.} =
|
||||
echo "Got from remote - ", string.fromBytes(await conn.readLp(1024))
|
||||
await conn.writeLp("Roger p2p!")
|
||||
|
||||
# We must close the connections ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
return T(codecs: @[TestCodec], handler: handle)
|
||||
return T.new(codecs = @[TestCodec], handler = handle)
|
||||
|
||||
##
|
||||
# Helper to create a switch/node
|
||||
##
|
||||
proc createSwitch(ma: MultiAddress, rng: ref BrHmacDrbgContext): Switch =
|
||||
proc createSwitch(ma: MultiAddress, rng: ref HmacDrbgContext): Switch =
|
||||
var switch = SwitchBuilder
|
||||
.new()
|
||||
.withRng(rng) # Give the application RNG
|
||||
@@ -41,7 +40,7 @@ proc createSwitch(ma: MultiAddress, rng: ref BrHmacDrbgContext): Switch =
|
||||
##
|
||||
# The actual application
|
||||
##
|
||||
proc main() {.async, gcsafe.} =
|
||||
proc main() {.async.} =
|
||||
let
|
||||
rng = newRng() # Single random number source for the whole application
|
||||
# port 0 will take a random available port
|
||||
|
||||
6
examples/index.md
Normal file
6
examples/index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# nim-libp2p documentation
|
||||
|
||||
Welcome to the nim-libp2p documentation!
|
||||
|
||||
Here, you'll find [tutorials](tutorial_1_connect.md) to help you get started, as well as
|
||||
the [full reference](https://status-im.github.io/nim-libp2p/master/libp2p.html).
|
||||
@@ -1,106 +0,0 @@
|
||||
Hi all, welcome to the first article of the nim-libp2p's tutorial series!
|
||||
|
||||
_This tutorial is for everyone who is interested in building peer-to-peer chatting applications. No Nim programming experience is needed._
|
||||
|
||||
To give you a quick overview, **Nim** is the programming language we are using and **nim-libp2p** is the Nim implementation of [libp2p](https://libp2p.io/), a modular library that enables the development of peer-to-peer network applications.
|
||||
|
||||
Hope you'll find it helpful in your journey of learning. Happy coding! ;)
|
||||
|
||||
# Before you start
|
||||
The only prerequisite here is [Nim](https://nim-lang.org/), the programming language with a Python-like syntax and a performance similar to C. Detailed information can be found [here](https://nim-lang.org/docs/tut1.html).
|
||||
|
||||
Install Nim via their official website: [https://nim-lang.org/install.html](https://nim-lang.org/install.html)
|
||||
Check Nim's installation via `nim --version` and its package manager Nimble via `nimble --version`.
|
||||
|
||||
You can now install the latest version of `nim-libp2p`:
|
||||
```bash
|
||||
nimble install libp2p@#master
|
||||
```
|
||||
|
||||
# A simple ping application
|
||||
We'll start by creating a simple application, which is starting two libp2p [switch](https://docs.libp2p.io/concepts/stream-multiplexing/#switch-swarm), and pinging each other using the [Ping](https://docs.libp2p.io/concepts/protocols/#ping) protocol.
|
||||
|
||||
_TIP: You can extract the code from this tutorial by running `nim c -r tools/markdown_runner.nim examples/tutorial_1_connect.md` in the libp2p folder!_
|
||||
|
||||
Let's create a `part1.nim`, and import our dependencies:
|
||||
```nim
|
||||
import bearssl
|
||||
import chronos
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/ping
|
||||
```
|
||||
[bearssl](https://github.com/status-im/nim-bearssl) is used as a [cryptographic pseudorandom number generator](https://en.wikipedia.org/wiki/Cryptographically-secure_pseudorandom_number_generator)
|
||||
[chronos](https://github.com/status-im/nim-chronos) the asynchronous framework used by `nim-libp2p`
|
||||
|
||||
Next, we'll create an helper procedure to create our switches. A switch needs a bit of configuration, and it will be easier to do this configuration only once:
|
||||
```nim
|
||||
proc createSwitch(ma: MultiAddress, rng: ref BrHmacDrbgContext): Switch =
|
||||
var switch = SwitchBuilder
|
||||
.new()
|
||||
.withRng(rng) # Give the application RNG
|
||||
.withAddress(ma) # Our local address(es)
|
||||
.withTcpTransport() # Use TCP as transport
|
||||
.withMplex() # Use Mplex as muxer
|
||||
.withNoise() # Use Noise as secure manager
|
||||
.build()
|
||||
|
||||
return switch
|
||||
```
|
||||
This will create a switch using [Mplex](https://docs.libp2p.io/concepts/stream-multiplexing/) as a multiplexer, Noise to secure the communication, and TCP as an underlying transport.
|
||||
|
||||
You can of course tweak this, to use a different or multiple transport, or tweak the configuration of Mplex and Noise, but this is some sane defaults that we'll use going forward.
|
||||
|
||||
|
||||
Let's now start to create our main procedure:
|
||||
```nim
|
||||
proc main() {.async, gcsafe.} =
|
||||
let
|
||||
rng = newRng()
|
||||
localAddress = MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
|
||||
pingProtocol = Ping.new(rng=rng)
|
||||
```
|
||||
We created some variables that we'll need for the rest of the application: the global `rng` instance, our `localAddress`, and an instance of the `Ping` protocol.
|
||||
The address is in the [MultiAddress](https://github.com/multiformats/multiaddr) format. The port `0` means "take any port available".
|
||||
|
||||
`tryGet` is procedure which is part of the [nim-result](https://github.com/arnetheduck/nim-result/), that will throw an exception if the supplied MultiAddress is not valid.
|
||||
|
||||
We can now create our two switches:
|
||||
```nim
|
||||
let
|
||||
switch1 = createSwitch(localAddress, rng)
|
||||
switch2 = createSwitch(localAddress, rng)
|
||||
|
||||
switch1.mount(pingProtocol)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
```
|
||||
We've **mounted** the `pingProtocol` on our first switch. This means that the first switch will actually listen for any ping requests coming in, and handle them accordingly.
|
||||
|
||||
Now that we've started the nodes, they are listening for incoming peers.
|
||||
We can find out which port was attributed, and the resulting local addresses, by using `switch1.peerInfo.addrs`.
|
||||
|
||||
We'll **dial** the first switch from the second one, by specifying it's **Peer ID**, it's **MultiAddress** and the **`Ping` protocol codec**:
|
||||
```nim
|
||||
let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, PingCodec)
|
||||
```
|
||||
We now have a `Ping` connection setup between the second and the first switch, we can use it to actually ping the node:
|
||||
```nim
|
||||
# ping the other node and echo the ping duration
|
||||
echo "ping: ", await pingProtocol.ping(conn)
|
||||
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
```
|
||||
|
||||
And that's it! Just a little bit of cleanup: shutting down the switches, waiting for them to stop, and we'll call our `main` procedure:
|
||||
```nim
|
||||
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||||
|
||||
waitFor(main())
|
||||
```
|
||||
|
||||
You can now run this program using `nim c -r part1.nim`, and you should see the dialing sequence, ending with a ping output.
|
||||
|
||||
In the [next tutorial](tutorial_2_customproto.md), we'll look at how to create our own custom protocol.
|
||||
95
examples/tutorial_1_connect.nim
Normal file
95
examples/tutorial_1_connect.nim
Normal file
@@ -0,0 +1,95 @@
|
||||
## # Simple ping tutorial
|
||||
##
|
||||
## Hi all, welcome to the first nim-libp2p tutorial!
|
||||
##
|
||||
## !!! tips ""
|
||||
## This tutorial is for everyone who is interested in building peer-to-peer applications. No Nim programming experience is needed.
|
||||
##
|
||||
## To give you a quick overview, **Nim** is the programming language we are using and **nim-libp2p** is the Nim implementation of [libp2p](https://libp2p.io/), a modular library that enables the development of peer-to-peer network applications.
|
||||
##
|
||||
## Hope you'll find it helpful in your journey of learning. Happy coding! ;)
|
||||
##
|
||||
## ## Before you start
|
||||
## The only prerequisite here is [Nim](https://nim-lang.org/), the programming language with a Python-like syntax and a performance similar to C. Detailed information can be found [here](https://nim-lang.org/docs/tut1.html).
|
||||
##
|
||||
## Install Nim via their [official website](https://nim-lang.org/install.html).
|
||||
## Check Nim's installation via `nim --version` and its package manager Nimble via `nimble --version`.
|
||||
##
|
||||
## You can now install the latest version of `nim-libp2p`:
|
||||
## ```bash
|
||||
## nimble install libp2p@#master
|
||||
## ```
|
||||
##
|
||||
## ## A simple ping application
|
||||
## We'll start by creating a simple application, which is starting two libp2p [switch](https://docs.libp2p.io/concepts/stream-multiplexing/#switch-swarm), and pinging each other using the [Ping](https://docs.libp2p.io/concepts/protocols/#ping) protocol.
|
||||
##
|
||||
## !!! tips ""
|
||||
## You can find the source of this tutorial (and other tutorials) in the [libp2p/examples](https://github.com/status-im/nim-libp2p/tree/master/examples) folder!
|
||||
##
|
||||
## Let's create a `part1.nim`, and import our dependencies:
|
||||
import chronos
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/ping
|
||||
|
||||
## [chronos](https://github.com/status-im/nim-chronos) the asynchronous framework used by `nim-libp2p`
|
||||
##
|
||||
## Next, we'll create an helper procedure to create our switches. A switch needs a bit of configuration, and it will be easier to do this configuration only once:
|
||||
proc createSwitch(ma: MultiAddress, rng: ref HmacDrbgContext): Switch =
|
||||
var switch = SwitchBuilder
|
||||
.new()
|
||||
.withRng(rng) # Give the application RNG
|
||||
.withAddress(ma) # Our local address(es)
|
||||
.withTcpTransport() # Use TCP as transport
|
||||
.withMplex() # Use Mplex as muxer
|
||||
.withNoise() # Use Noise as secure manager
|
||||
.build()
|
||||
|
||||
return switch
|
||||
|
||||
## This will create a switch using [Mplex](https://docs.libp2p.io/concepts/stream-multiplexing/) as a multiplexer, Noise to secure the communication, and TCP as an underlying transport.
|
||||
##
|
||||
## You can of course tweak this, to use a different or multiple transport, or tweak the configuration of Mplex and Noise, but this is some sane defaults that we'll use going forward.
|
||||
##
|
||||
##
|
||||
## Let's now start to create our main procedure:
|
||||
proc main() {.async.} =
|
||||
let
|
||||
rng = newRng()
|
||||
localAddress = MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
|
||||
pingProtocol = Ping.new(rng=rng)
|
||||
## We created some variables that we'll need for the rest of the application: the global `rng` instance, our `localAddress`, and an instance of the `Ping` protocol.
|
||||
## The address is in the [MultiAddress](https://github.com/multiformats/multiaddr) format. The port `0` means "take any port available".
|
||||
##
|
||||
## `tryGet` is procedure which is part of [nim-result](https://github.com/arnetheduck/nim-result/), that will throw an exception if the supplied MultiAddress is invalid.
|
||||
##
|
||||
## We can now create our two switches:
|
||||
let
|
||||
switch1 = createSwitch(localAddress, rng)
|
||||
switch2 = createSwitch(localAddress, rng)
|
||||
|
||||
switch1.mount(pingProtocol)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
## We've **mounted** the `pingProtocol` on our first switch. This means that the first switch will actually listen for any ping requests coming in, and handle them accordingly.
|
||||
##
|
||||
## Now that we've started the nodes, they are listening for incoming peers.
|
||||
## We can find out which port was attributed, and the resulting local addresses, by using `switch1.peerInfo.addrs`.
|
||||
##
|
||||
## We'll **dial** the first switch from the second one, by specifying it's **Peer ID**, it's **MultiAddress** and the **`Ping` protocol codec**:
|
||||
let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, PingCodec)
|
||||
## We now have a `Ping` connection setup between the second and the first switch, we can use it to actually ping the node:
|
||||
# ping the other node and echo the ping duration
|
||||
echo "ping: ", await pingProtocol.ping(conn)
|
||||
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
## And that's it! Just a little bit of cleanup: shutting down the switches, waiting for them to stop, and we'll call our `main` procedure:
|
||||
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||||
|
||||
waitFor(main())
|
||||
|
||||
## You can now run this program using `nim c -r part1.nim`, and you should see the dialing sequence, ending with a ping output.
|
||||
##
|
||||
## In the [next tutorial](tutorial_2_customproto.md), we'll look at how to create our own custom protocol.
|
||||
@@ -1,80 +0,0 @@
|
||||
In the [previous tutorial](tutorial_1_connect.md), we've looked at how to create a simple ping program using the `nim-libp2p`.
|
||||
|
||||
We'll now look at how to create a custom protocol inside the libp2p
|
||||
|
||||
# Custom protocol in libp2p
|
||||
Let's create a `part2.nim`, and import our dependencies:
|
||||
```nim
|
||||
import bearssl
|
||||
import chronos
|
||||
import stew/byteutils
|
||||
|
||||
import libp2p
|
||||
```
|
||||
This is similar to the first tutorial, except we don't need to import the `Ping` protocol.
|
||||
|
||||
Next, we'll declare our custom protocol
|
||||
```nim
|
||||
const TestCodec = "/test/proto/1.0.0"
|
||||
|
||||
type TestProto = ref object of LPProtocol
|
||||
```
|
||||
|
||||
We've set a [protocol ID](https://docs.libp2p.io/concepts/protocols/#protocol-ids), and created a custom `LPProtocol`. In a more complex protocol, we could use this structure to store interesting variables.
|
||||
|
||||
A protocol generally has two part: and handling/server part, and a dialing/client part.
|
||||
Theses two parts can be identical, but in our trivial protocol, the server will wait for a message from the client, and the client will send a message, so we have to handle the two cases separately.
|
||||
|
||||
Let's start with the server part:
|
||||
```nim
|
||||
proc new(T: typedesc[TestProto]): T =
|
||||
# every incoming connections will in be handled in this closure
|
||||
proc handle(conn: Connection, proto: string) {.async, gcsafe.} =
|
||||
echo "Got from remote - ", string.fromBytes(await conn.readLp(1024))
|
||||
# We must close the connections ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
return T(codecs: @[TestCodec], handler: handle)
|
||||
```
|
||||
This is a constructor for our `TestProto`, that will specify our `codecs` and a `handler`, which will be called for each incoming peer asking for this protocol.
|
||||
In our handle, we simply read a message from the connection and `echo` it.
|
||||
|
||||
We can now create our client part:
|
||||
```nim
|
||||
proc hello(p: TestProto, conn: Connection) {.async.} =
|
||||
await conn.writeLp("Hello p2p!")
|
||||
```
|
||||
Again, pretty straight-forward, we just send a message on the connection.
|
||||
|
||||
We can now create our main procedure:
|
||||
```nim
|
||||
proc main() {.async, gcsafe.} =
|
||||
let
|
||||
rng = newRng()
|
||||
testProto = TestProto.new()
|
||||
switch1 = newStandardSwitch(rng=rng)
|
||||
switch2 = newStandardSwitch(rng=rng)
|
||||
|
||||
switch1.mount(testProto)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
|
||||
let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, TestCodec)
|
||||
|
||||
await testProto.hello(conn)
|
||||
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||||
```
|
||||
|
||||
This is very similar to the first tutorial's `main`, the only noteworthy difference is that we use `newStandardSwitch`, which is similar to `createSwitch` but is bundled directly in libp2p
|
||||
|
||||
We can now wrap our program by calling our main proc:
|
||||
```nim
|
||||
waitFor(main())
|
||||
```
|
||||
|
||||
And that's it!
|
||||
74
examples/tutorial_2_customproto.nim
Normal file
74
examples/tutorial_2_customproto.nim
Normal file
@@ -0,0 +1,74 @@
|
||||
## # Custom protocol in libp2p
|
||||
##
|
||||
## In the [previous tutorial](tutorial_1_connect.md), we've looked at how to create a simple ping program using the `nim-libp2p`.
|
||||
##
|
||||
## We'll now look at how to create a custom protocol inside the libp2p
|
||||
##
|
||||
## Let's create a `part2.nim`, and import our dependencies:
|
||||
import chronos
|
||||
import stew/byteutils
|
||||
|
||||
import libp2p
|
||||
## This is similar to the first tutorial, except we don't need to import the `Ping` protocol.
|
||||
##
|
||||
## Next, we'll declare our custom protocol
|
||||
const TestCodec = "/test/proto/1.0.0"
|
||||
|
||||
type TestProto = ref object of LPProtocol
|
||||
|
||||
## We've set a [protocol ID](https://docs.libp2p.io/concepts/protocols/#protocol-ids), and created a custom `LPProtocol`. In a more complex protocol, we could use this structure to store interesting variables.
|
||||
##
|
||||
## A protocol generally has two part: and handling/server part, and a dialing/client part.
|
||||
## Theses two parts can be identical, but in our trivial protocol, the server will wait for a message from the client, and the client will send a message, so we have to handle the two cases separately.
|
||||
##
|
||||
## Let's start with the server part:
|
||||
|
||||
proc new(T: typedesc[TestProto]): T =
|
||||
# every incoming connections will in be handled in this closure
|
||||
proc handle(conn: Connection, proto: string) {.async.} =
|
||||
# Read up to 1024 bytes from this connection, and transform them into
|
||||
# a string
|
||||
echo "Got from remote - ", string.fromBytes(await conn.readLp(1024))
|
||||
# We must close the connections ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
return T.new(codecs = @[TestCodec], handler = handle)
|
||||
|
||||
## This is a constructor for our `TestProto`, that will specify our `codecs` and a `handler`, which will be called for each incoming peer asking for this protocol.
|
||||
## In our handle, we simply read a message from the connection and `echo` it.
|
||||
##
|
||||
## We can now create our client part:
|
||||
proc hello(p: TestProto, conn: Connection) {.async.} =
|
||||
await conn.writeLp("Hello p2p!")
|
||||
|
||||
## Again, pretty straight-forward, we just send a message on the connection.
|
||||
##
|
||||
## We can now create our main procedure:
|
||||
proc main() {.async.} =
|
||||
let
|
||||
rng = newRng()
|
||||
testProto = TestProto.new()
|
||||
switch1 = newStandardSwitch(rng=rng)
|
||||
switch2 = newStandardSwitch(rng=rng)
|
||||
|
||||
switch1.mount(testProto)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
|
||||
let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, TestCodec)
|
||||
|
||||
await testProto.hello(conn)
|
||||
|
||||
# We must close the connection ourselves when we're done with it
|
||||
await conn.close()
|
||||
|
||||
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||||
|
||||
## This is very similar to the first tutorial's `main`, the only noteworthy difference is that we use `newStandardSwitch`, which is similar to the `createSwitch` of the first tutorial, but is bundled directly in libp2p
|
||||
##
|
||||
## We can now wrap our program by calling our main proc:
|
||||
waitFor(main())
|
||||
|
||||
## And that's it!
|
||||
## In the [next tutorial](tutorial_3_protobuf.md), we'll create a more complex protocol using Protobuf.
|
||||
162
examples/tutorial_3_protobuf.nim
Normal file
162
examples/tutorial_3_protobuf.nim
Normal file
@@ -0,0 +1,162 @@
|
||||
## # Protobuf usage
|
||||
##
|
||||
## In the [previous tutorial](tutorial_2_customproto.md), we created a simple "ping" protocol.
|
||||
## Most real protocol want their messages to be structured and extensible, which is why
|
||||
## most real protocols use [protobuf](https://developers.google.com/protocol-buffers) to
|
||||
## define their message structures.
|
||||
##
|
||||
## Here, we'll create a slightly more complex protocol, which parses & generate protobuf
|
||||
## messages. Let's start by importing our dependencies, as usual:
|
||||
import chronos
|
||||
import stew/results # for Opt[T]
|
||||
|
||||
import libp2p
|
||||
|
||||
## ## Protobuf encoding & decoding
|
||||
## This will be the structure of our messages:
|
||||
## ```protobuf
|
||||
## message MetricList {
|
||||
## message Metric {
|
||||
## string name = 1;
|
||||
## float value = 2;
|
||||
## }
|
||||
##
|
||||
## repeated Metric metrics = 2;
|
||||
## }
|
||||
## ```
|
||||
## We'll create our protobuf types, encoders & decoders, according to this format.
|
||||
## To create the encoders & decoders, we are going to use minprotobuf
|
||||
## (included in libp2p).
|
||||
##
|
||||
## While more modern technics
|
||||
## (such as [nim-protobuf-serialization](https://github.com/status-im/nim-protobuf-serialization))
|
||||
## exists, minprotobuf is currently the recommended method to handle protobuf, since it has
|
||||
## been used in production extensively, and audited.
|
||||
type
|
||||
Metric = object
|
||||
name: string
|
||||
value: float
|
||||
|
||||
MetricList = object
|
||||
metrics: seq[Metric]
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
proc encode(m: Metric): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
result.write(1, m.name)
|
||||
result.write(2, m.value)
|
||||
result.finish()
|
||||
|
||||
proc decode(_: type Metric, buf: seq[byte]): Result[Metric, ProtoError] =
|
||||
var res: Metric
|
||||
let pb = initProtoBuffer(buf)
|
||||
# "getField" will return a Result[bool, ProtoError].
|
||||
# The Result will hold an error if the protobuf is invalid.
|
||||
# The Result will hold "false" if the field is missing
|
||||
#
|
||||
# We are just checking the error, and ignoring whether the value
|
||||
# is present or not (default values are valid).
|
||||
discard ? pb.getField(1, res.name)
|
||||
discard ? pb.getField(2, res.value)
|
||||
ok(res)
|
||||
|
||||
proc encode(m: MetricList): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
for metric in m.metrics:
|
||||
result.write(1, metric.encode())
|
||||
result.finish()
|
||||
|
||||
proc decode(_: type MetricList, buf: seq[byte]): Result[MetricList, ProtoError] =
|
||||
var
|
||||
res: MetricList
|
||||
metrics: seq[seq[byte]]
|
||||
let pb = initProtoBuffer(buf)
|
||||
discard ? pb.getRepeatedField(1, metrics)
|
||||
|
||||
for metric in metrics:
|
||||
res.metrics &= ? Metric.decode(metric)
|
||||
ok(res)
|
||||
|
||||
## ## Results instead of exceptions
|
||||
## As you can see, this part of the program also uses Results instead of exceptions for error handling.
|
||||
## We start by `{.push raises: [].}`, which will prevent every non-async function from raising
|
||||
## exceptions.
|
||||
##
|
||||
## Then, we use [nim-result](https://github.com/arnetheduck/nim-result) to convey
|
||||
## errors to function callers. A `Result[T, E]` will either hold a valid result of type
|
||||
## T, or an error of type E.
|
||||
##
|
||||
## You can check if the call succeeded by using `res.isOk`, and then get the
|
||||
## value using `res.value` or the error by using `res.error`.
|
||||
##
|
||||
## Another useful tool is `?`, which will unpack a Result if it succeeded,
|
||||
## or if it failed, exit the current procedure returning the error.
|
||||
##
|
||||
## nim-result is packed with other functionalities that you'll find in the
|
||||
## nim-result repository.
|
||||
##
|
||||
## Results and exception are generally interchangeable, but have different semantics
|
||||
## that you may or may not prefer.
|
||||
##
|
||||
## ## Creating the protocol
|
||||
## We'll next create a protocol, like in the last tutorial, to request these metrics from our host
|
||||
type
|
||||
MetricCallback = proc: Future[MetricList] {.raises: [], gcsafe.}
|
||||
MetricProto = ref object of LPProtocol
|
||||
metricGetter: MetricCallback
|
||||
|
||||
proc new(_: typedesc[MetricProto], cb: MetricCallback): MetricProto =
|
||||
var res: MetricProto
|
||||
proc handle(conn: Connection, proto: string) {.async.} =
|
||||
let
|
||||
metrics = await res.metricGetter()
|
||||
asProtobuf = metrics.encode()
|
||||
await conn.writeLp(asProtobuf.buffer)
|
||||
await conn.close()
|
||||
|
||||
res = MetricProto.new(@["/metric-getter/1.0.0"], handle)
|
||||
res.metricGetter = cb
|
||||
return res
|
||||
|
||||
proc fetch(p: MetricProto, conn: Connection): Future[MetricList] {.async.} =
|
||||
let protobuf = await conn.readLp(2048)
|
||||
# tryGet will raise an exception if the Result contains an error.
|
||||
# It's useful to bridge between exception-world and result-world
|
||||
return MetricList.decode(protobuf).tryGet()
|
||||
|
||||
## We can now create our main procedure:
|
||||
proc main() {.async.} =
|
||||
let rng = newRng()
|
||||
proc randomMetricGenerator: Future[MetricList] {.async.} =
|
||||
let metricCount = rng[].generate(uint32) mod 16
|
||||
for i in 0 ..< metricCount + 1:
|
||||
result.metrics.add(Metric(
|
||||
name: "metric_" & $i,
|
||||
value: float(rng[].generate(uint16)) / 1000.0
|
||||
))
|
||||
return result
|
||||
let
|
||||
metricProto1 = MetricProto.new(randomMetricGenerator)
|
||||
metricProto2 = MetricProto.new(randomMetricGenerator)
|
||||
switch1 = newStandardSwitch(rng=rng)
|
||||
switch2 = newStandardSwitch(rng=rng)
|
||||
|
||||
switch1.mount(metricProto1)
|
||||
|
||||
await switch1.start()
|
||||
await switch2.start()
|
||||
|
||||
let
|
||||
conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, metricProto2.codecs)
|
||||
metrics = await metricProto2.fetch(conn)
|
||||
await conn.close()
|
||||
|
||||
for metric in metrics.metrics:
|
||||
echo metric.name, " = ", metric.value
|
||||
|
||||
await allFutures(switch1.stop(), switch2.stop()) # close connections and shutdown all transports
|
||||
|
||||
waitFor(main())
|
||||
|
||||
## If you run this program, you should see random metrics being sent from the switch1 to the switch2.
|
||||
163
examples/tutorial_4_gossipsub.nim
Normal file
163
examples/tutorial_4_gossipsub.nim
Normal file
@@ -0,0 +1,163 @@
|
||||
## # GossipSub
|
||||
##
|
||||
## In this tutorial, we'll build a simple GossipSub network
|
||||
## to broadcast the metrics we built in the previous tutorial.
|
||||
##
|
||||
## GossipSub is used to broadcast some messages in a network,
|
||||
## and allows to balance between latency, bandwidth usage,
|
||||
## privacy and attack resistance.
|
||||
##
|
||||
## You'll find a good explanation on how GossipSub works
|
||||
## [here.](https://docs.libp2p.io/concepts/publish-subscribe/) There are a lot
|
||||
## of parameters you can tweak to adjust how GossipSub behaves but here we'll
|
||||
## use the sane defaults shipped with libp2p.
|
||||
##
|
||||
## We'll start by creating our metric structure like previously
|
||||
|
||||
import chronos
|
||||
import stew/results
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/pubsub/rpc/messages
|
||||
|
||||
type
|
||||
Metric = object
|
||||
name: string
|
||||
value: float
|
||||
|
||||
MetricList = object
|
||||
hostname: string
|
||||
metrics: seq[Metric]
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
proc encode(m: Metric): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
result.write(1, m.name)
|
||||
result.write(2, m.value)
|
||||
result.finish()
|
||||
|
||||
proc decode(_: type Metric, buf: seq[byte]): Result[Metric, ProtoError] =
|
||||
var res: Metric
|
||||
let pb = initProtoBuffer(buf)
|
||||
discard ? pb.getField(1, res.name)
|
||||
discard ? pb.getField(2, res.value)
|
||||
ok(res)
|
||||
|
||||
proc encode(m: MetricList): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
for metric in m.metrics:
|
||||
result.write(1, metric.encode())
|
||||
result.write(2, m.hostname)
|
||||
result.finish()
|
||||
|
||||
proc decode(_: type MetricList, buf: seq[byte]): Result[MetricList, ProtoError] =
|
||||
var
|
||||
res: MetricList
|
||||
metrics: seq[seq[byte]]
|
||||
let pb = initProtoBuffer(buf)
|
||||
discard ? pb.getRepeatedField(1, metrics)
|
||||
|
||||
for metric in metrics:
|
||||
res.metrics &= ? Metric.decode(metric)
|
||||
? pb.getRequiredField(2, res.hostname)
|
||||
ok(res)
|
||||
|
||||
## This is exactly like the previous structure, except that we added
|
||||
## a `hostname` to distinguish where the metric is coming from.
|
||||
##
|
||||
## Now we'll create a small GossipSub network to broadcast the metrics,
|
||||
## and collect them on one of the node.
|
||||
|
||||
type Node = tuple[switch: Switch, gossip: GossipSub, hostname: string]
|
||||
|
||||
proc oneNode(node: Node, rng: ref HmacDrbgContext) {.async.} =
|
||||
# This procedure will handle one of the node of the network
|
||||
node.gossip.addValidator(["metrics"],
|
||||
proc(topic: string, message: Message): Future[ValidationResult] {.async.} =
|
||||
let decoded = MetricList.decode(message.data)
|
||||
if decoded.isErr: return ValidationResult.Reject
|
||||
return ValidationResult.Accept
|
||||
)
|
||||
# This "validator" will attach to the `metrics` topic and make sure
|
||||
# that every message in this topic is valid. This allows us to stop
|
||||
# propagation of invalid messages quickly in the network, and punish
|
||||
# peers sending them.
|
||||
|
||||
# `John` will be responsible to log the metrics, the rest of the nodes
|
||||
# will just forward them in the network
|
||||
if node.hostname == "John":
|
||||
node.gossip.subscribe("metrics",
|
||||
proc (topic: string, data: seq[byte]) {.async.} =
|
||||
echo MetricList.decode(data).tryGet()
|
||||
)
|
||||
else:
|
||||
node.gossip.subscribe("metrics", nil)
|
||||
|
||||
# Create random metrics 10 times and broadcast them
|
||||
for _ in 0..<10:
|
||||
await sleepAsync(500.milliseconds)
|
||||
var metricList = MetricList(hostname: node.hostname)
|
||||
let metricCount = rng[].generate(uint32) mod 4
|
||||
for i in 0 ..< metricCount + 1:
|
||||
metricList.metrics.add(Metric(
|
||||
name: "metric_" & $i,
|
||||
value: float(rng[].generate(uint16)) / 1000.0
|
||||
))
|
||||
|
||||
discard await node.gossip.publish("metrics", encode(metricList).buffer)
|
||||
await node.switch.stop()
|
||||
|
||||
## For our main procedure, we'll create a few nodes, and connect them together.
|
||||
## Note that they are not all interconnected, but GossipSub will take care of
|
||||
## broadcasting to the full network nonetheless.
|
||||
proc main {.async.} =
|
||||
let rng = newRng()
|
||||
var nodes: seq[Node]
|
||||
|
||||
for hostname in ["John", "Walter", "David", "Thuy", "Amy"]:
|
||||
let
|
||||
switch = newStandardSwitch(rng=rng)
|
||||
gossip = GossipSub.init(switch = switch, triggerSelf = true)
|
||||
switch.mount(gossip)
|
||||
await switch.start()
|
||||
|
||||
nodes.add((switch, gossip, hostname))
|
||||
|
||||
for index, node in nodes:
|
||||
# Connect to a few neighbors
|
||||
for otherNodeIdx in index - 1 .. index + 2:
|
||||
if otherNodeIdx notin 0 ..< nodes.len or otherNodeIdx == index: continue
|
||||
let otherNode = nodes[otherNodeIdx]
|
||||
await node.switch.connect(
|
||||
otherNode.switch.peerInfo.peerId,
|
||||
otherNode.switch.peerInfo.addrs)
|
||||
|
||||
var allFuts: seq[Future[void]]
|
||||
for node in nodes:
|
||||
allFuts.add(oneNode(node, rng))
|
||||
|
||||
await allFutures(allFuts)
|
||||
|
||||
waitFor(main())
|
||||
|
||||
## If you run this program, you should see something like:
|
||||
## ```
|
||||
## (hostname: "John", metrics: @[(name: "metric_0", value: 42.097), (name: "metric_1", value: 50.99), (name: "metric_2", value: 47.86), (name: "metric_3", value: 5.368)])
|
||||
## (hostname: "Walter", metrics: @[(name: "metric_0", value: 39.452), (name: "metric_1", value: 15.606), (name: "metric_2", value: 14.059), (name: "metric_3", value: 6.68)])
|
||||
## (hostname: "David", metrics: @[(name: "metric_0", value: 9.82), (name: "metric_1", value: 2.862), (name: "metric_2", value: 15.514)])
|
||||
## (hostname: "Thuy", metrics: @[(name: "metric_0", value: 59.038)])
|
||||
## (hostname: "Amy", metrics: @[(name: "metric_0", value: 55.616), (name: "metric_1", value: 23.52), (name: "metric_2", value: 59.081), (name: "metric_3", value: 2.516)])
|
||||
## ```
|
||||
##
|
||||
## This is John receiving & logging everyone's metrics.
|
||||
##
|
||||
## ## Going further
|
||||
## Building efficient & safe GossipSub networks is a tricky subject. By tweaking the [gossip params](https://status-im.github.io/nim-libp2p/master/libp2p/protocols/pubsub/gossipsub/types.html#GossipSubParams)
|
||||
## and [topic params](https://status-im.github.io/nim-libp2p/master/libp2p/protocols/pubsub/gossipsub/types.html#TopicParams),
|
||||
## you can achieve very different properties.
|
||||
##
|
||||
## Also see reports for [GossipSub v1.1](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4)
|
||||
##
|
||||
## If you are interested in broadcasting for your application, you may want to use [Waku](https://waku.org/), which builds on top of GossipSub,
|
||||
## and adds features such as history, spam protection, and light node friendliness.
|
||||
132
examples/tutorial_5_discovery.nim
Normal file
132
examples/tutorial_5_discovery.nim
Normal file
@@ -0,0 +1,132 @@
|
||||
## # Discovery Manager
|
||||
##
|
||||
## In the [previous tutorial](tutorial_4_gossipsub.md), we built a custom protocol using [protobuf](https://developers.google.com/protocol-buffers) and
|
||||
## spread informations (some metrics) on the network using gossipsub.
|
||||
## For this tutorial, on the other hand, we'll go back on a simple example
|
||||
## we'll try to discover a specific peers to greet on the network.
|
||||
##
|
||||
## First, as usual, we import the dependencies:
|
||||
import sequtils
|
||||
import chronos
|
||||
import stew/byteutils
|
||||
|
||||
import libp2p
|
||||
import libp2p/protocols/rendezvous
|
||||
import libp2p/discovery/rendezvousinterface
|
||||
import libp2p/discovery/discoverymngr
|
||||
|
||||
## We'll not use newStandardSwitch this time as we need the discovery protocol
|
||||
## [RendezVous](https://github.com/libp2p/specs/blob/master/rendezvous/README.md) to be mounted on the switch using withRendezVous.
|
||||
##
|
||||
## Note that other discovery methods such as [Kademlia](https://github.com/libp2p/specs/blob/master/kad-dht/README.md) or [discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md) exist.
|
||||
proc createSwitch(rdv: RendezVous = RendezVous.new()): Switch =
|
||||
SwitchBuilder.new()
|
||||
.withRng(newRng())
|
||||
.withAddresses(@[ MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet() ])
|
||||
.withTcpTransport()
|
||||
.withYamux()
|
||||
.withNoise()
|
||||
.withRendezVous(rdv)
|
||||
.build()
|
||||
|
||||
# Create a really simple protocol to log one message received then close the stream
|
||||
const DumbCodec = "/dumb/proto/1.0.0"
|
||||
type DumbProto = ref object of LPProtocol
|
||||
proc new(T: typedesc[DumbProto], nodeNumber: int): T =
|
||||
proc handle(conn: Connection, proto: string) {.async.} =
|
||||
echo "Node", nodeNumber, " received: ", string.fromBytes(await conn.readLp(1024))
|
||||
await conn.close()
|
||||
return T.new(codecs = @[DumbCodec], handler = handle)
|
||||
|
||||
## ## Bootnodes
|
||||
## The first time a p2p program is ran, he needs to know how to join
|
||||
## its network. This is generally done by hard-coding a list of stable
|
||||
## nodes in the binary, called "bootnodes". These bootnodes are a
|
||||
## critical part of a p2p network, since they are used by every new
|
||||
## user to onboard the network.
|
||||
##
|
||||
## By using libp2p, we can use any node supporting our discovery protocol
|
||||
## (rendezvous in this case) as a bootnode. For this example, we'll
|
||||
## create a bootnode, and then every peer will advertise itself on the
|
||||
## bootnode, and use it to find other peers
|
||||
proc main() {.async.} =
|
||||
let bootNode = createSwitch()
|
||||
await bootNode.start()
|
||||
|
||||
# Create 5 nodes in the network
|
||||
var
|
||||
switches: seq[Switch] = @[]
|
||||
discManagers: seq[DiscoveryManager] = @[]
|
||||
|
||||
for i in 0..5:
|
||||
let rdv = RendezVous.new()
|
||||
# Create a remote future to await at the end of the program
|
||||
let switch = createSwitch(rdv)
|
||||
switch.mount(DumbProto.new(i))
|
||||
switches.add(switch)
|
||||
|
||||
# A discovery manager is a simple tool, you can set it up by adding discovery
|
||||
# interfaces (such as RendezVousInterface) then you can use it to advertise
|
||||
# something on the network or to request something from it.
|
||||
let dm = DiscoveryManager()
|
||||
# A RendezVousInterface is a RendezVous protocol wrapped to be usable by the
|
||||
# DiscoveryManager.
|
||||
dm.add(RendezVousInterface.new(rdv))
|
||||
discManagers.add(dm)
|
||||
|
||||
# We can now start the switch and connect to the bootnode
|
||||
await switch.start()
|
||||
await switch.connect(bootNode.peerInfo.peerId, bootNode.peerInfo.addrs)
|
||||
|
||||
# Each nodes of the network will advertise on some topics (EvenGang or OddClub)
|
||||
dm.advertise(RdvNamespace(if i mod 2 == 0: "EvenGang" else: "OddClub"))
|
||||
|
||||
## We can now create the newcomer. This peer will connect to the boot node, and use
|
||||
## it to discover peers & greet them.
|
||||
let
|
||||
rdv = RendezVous.new()
|
||||
newcomer = createSwitch(rdv)
|
||||
dm = DiscoveryManager()
|
||||
await newcomer.start()
|
||||
await newcomer.connect(bootNode.peerInfo.peerId, bootNode.peerInfo.addrs)
|
||||
dm.add(RendezVousInterface.new(rdv, ttr = 250.milliseconds))
|
||||
|
||||
# Use the discovery manager to find peers on the OddClub topic to greet them
|
||||
let queryOddClub = dm.request(RdvNamespace("OddClub"))
|
||||
for _ in 0..2:
|
||||
let
|
||||
# getPeer give you a PeerAttribute containing informations about the peer.
|
||||
res = await queryOddClub.getPeer()
|
||||
# Here we will use the PeerId and the MultiAddress to greet him
|
||||
conn = await newcomer.dial(res[PeerId], res.getAll(MultiAddress), DumbCodec)
|
||||
await conn.writeLp("Odd Club suuuucks! Even Gang is better!")
|
||||
# Uh-oh!
|
||||
await conn.close()
|
||||
# Wait for the peer to close the stream
|
||||
await conn.join()
|
||||
# Queries will run in a loop, so we must stop them when we are done
|
||||
queryOddClub.stop()
|
||||
|
||||
# Maybe it was because he wanted to join the EvenGang
|
||||
let queryEvenGang = dm.request(RdvNamespace("EvenGang"))
|
||||
for _ in 0..2:
|
||||
let
|
||||
res = await queryEvenGang.getPeer()
|
||||
conn = await newcomer.dial(res[PeerId], res.getAll(MultiAddress), DumbCodec)
|
||||
await conn.writeLp("Even Gang is sooo laaame! Odd Club rocks!")
|
||||
# Or maybe not...
|
||||
await conn.close()
|
||||
await conn.join()
|
||||
queryEvenGang.stop()
|
||||
# What can I say, some people just want to watch the world burn... Anyway
|
||||
|
||||
# Stop all the discovery managers
|
||||
for d in discManagers:
|
||||
d.stop()
|
||||
dm.stop()
|
||||
|
||||
# Stop all the switches
|
||||
await allFutures(switches.mapIt(it.stop()))
|
||||
await allFutures(bootNode.stop(), newcomer.stop())
|
||||
|
||||
waitFor(main())
|
||||
259
examples/tutorial_6_game.nim
Normal file
259
examples/tutorial_6_game.nim
Normal file
@@ -0,0 +1,259 @@
|
||||
## # Tron example
|
||||
##
|
||||
## In this tutorial, we will create a video game based on libp2p, using
|
||||
## all of the features we talked about in the last tutorials.
|
||||
##
|
||||
## We will:
|
||||
## - Discover peers using the Discovery Manager
|
||||
## - Use GossipSub to find a play mate
|
||||
## - Create a custom protocol to play with him
|
||||
##
|
||||
## While this may look like a daunting project, it's less than 150 lines of code.
|
||||
##
|
||||
## The game will be a simple Tron. We will use [nico](https://github.com/ftsf/nico)
|
||||
## as a game engine. (you need to run `nimble install nico` to have it available)
|
||||
##
|
||||
## 
|
||||
##
|
||||
## We will start by importing our dependencies and creating our types
|
||||
import os
|
||||
import nico, chronos, stew/byteutils, stew/endians2
|
||||
import libp2p
|
||||
import libp2p/protocols/rendezvous
|
||||
import libp2p/discovery/rendezvousinterface
|
||||
import libp2p/discovery/discoverymngr
|
||||
|
||||
const
|
||||
directions = @[(K_UP, 0, -1), (K_LEFT, -1, 0), (K_DOWN, 0, 1), (K_RIGHT, 1, 0)]
|
||||
mapSize = 32
|
||||
tickPeriod = 0.2
|
||||
|
||||
type
|
||||
Player = ref object
|
||||
x, y: int
|
||||
currentDir, nextDir: int
|
||||
lost: bool
|
||||
color: int
|
||||
|
||||
Game = ref object
|
||||
gameMap: array[mapSize * mapSize, int]
|
||||
tickTime: float
|
||||
localPlayer, remotePlayer: Player
|
||||
peerFound: Future[Connection]
|
||||
hasCandidate: bool
|
||||
tickFinished: Future[int]
|
||||
|
||||
GameProto = ref object of LPProtocol
|
||||
|
||||
proc new(_: type[Game]): Game =
|
||||
# Default state of a game
|
||||
result = Game(
|
||||
tickTime: -3.0, # 3 seconds of "warm-up" time
|
||||
localPlayer: Player(x: 4, y: 16, currentDir: 3, nextDir: 3, color: 8),
|
||||
remotePlayer: Player(x: 27, y: 16, currentDir: 1, nextDir: 1, color: 12),
|
||||
peerFound: newFuture[Connection]()
|
||||
)
|
||||
for pos in 0 .. result.gameMap.high:
|
||||
if pos mod mapSize in [0, mapSize - 1] or pos div mapSize in [0, mapSize - 1]:
|
||||
result.gameMap[pos] = 7
|
||||
|
||||
## ## Game Logic
|
||||
## The networking during the game will work like this:
|
||||
##
|
||||
## * Each player will have `tickPeriod` (0.1) seconds to choose
|
||||
## a direction that he wants to go to (default to current direction)
|
||||
## * After `tickPeriod`, we will send our choosen direction to the peer,
|
||||
## and wait for his direction
|
||||
## * Once we have both direction, we will "tick" the game, and restart the
|
||||
## loop, as long as both player are alive.
|
||||
##
|
||||
## This is a very simplistic scheme, but creating proper networking for
|
||||
## video games is an [art](https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization)
|
||||
##
|
||||
## The main drawback of this scheme is that the more ping you have with
|
||||
## the peer, the slower the game will run. Or invertedly, the less ping you
|
||||
## have, the faster it runs!
|
||||
proc update(g: Game, dt: float32) =
|
||||
# Will be called at each frame of the game.
|
||||
#
|
||||
# Because both Nico and Chronos have a main loop,
|
||||
# they must share the control of the main thread.
|
||||
# This is a hacky way to make this happen
|
||||
waitFor(sleepAsync(1.milliseconds))
|
||||
# Don't do anything if we are still waiting for an opponent
|
||||
if not(g.peerFound.finished()) or isNil(g.tickFinished): return
|
||||
g.tickTime += dt
|
||||
|
||||
# Update the wanted direction, making sure we can't go backward
|
||||
for i in 0 .. directions.high:
|
||||
if i != (g.localPlayer.currentDir + 2 mod 4) and keyp(directions[i][0]):
|
||||
g.localPlayer.nextDir = i
|
||||
|
||||
if g.tickTime > tickPeriod and not g.tickFinished.finished():
|
||||
# We choosen our next direction, let the networking know
|
||||
g.localPlayer.currentDir = g.localPlayer.nextDir
|
||||
g.tickFinished.complete(g.localPlayer.currentDir)
|
||||
|
||||
proc tick(g: Game, p: Player) =
|
||||
# Move player and check if he lost
|
||||
p.x += directions[p.currentDir][1]
|
||||
p.y += directions[p.currentDir][2]
|
||||
if g.gameMap[p.y * mapSize + p.x] != 0: p.lost = true
|
||||
g.gameMap[p.y * mapSize + p.x] = p.color
|
||||
|
||||
proc mainLoop(g: Game, peer: Connection) {.async.} =
|
||||
while not (g.localPlayer.lost or g.remotePlayer.lost):
|
||||
if g.tickTime > 0.0:
|
||||
g.tickTime = 0
|
||||
g.tickFinished = newFuture[int]()
|
||||
|
||||
# Wait for a choosen direction
|
||||
let dir = await g.tickFinished
|
||||
# Send it
|
||||
await peer.writeLp(toBytes(uint32(dir)))
|
||||
|
||||
# Get the one from the peer
|
||||
g.remotePlayer.currentDir = int uint32.fromBytes(await peer.readLp(8))
|
||||
# Tick the players & restart
|
||||
g.tick(g.remotePlayer)
|
||||
g.tick(g.localPlayer)
|
||||
|
||||
## We'll draw the map & put some texts when necessary:
|
||||
proc draw(g: Game) =
|
||||
for pos, color in g.gameMap:
|
||||
setColor(color)
|
||||
boxFill(pos mod 32 * 4, pos div 32 * 4, 4, 4)
|
||||
let text = if not(g.peerFound.finished()): "Matchmaking.."
|
||||
elif g.tickTime < -1.5: "Welcome to Etron"
|
||||
elif g.tickTime < 0.0: "- " & $(int(abs(g.tickTime) / 0.5) + 1) & " -"
|
||||
elif g.remotePlayer.lost and g.localPlayer.lost: "DEUCE"
|
||||
elif g.localPlayer.lost: "YOU LOOSE"
|
||||
elif g.remotePlayer.lost: "YOU WON"
|
||||
else: ""
|
||||
printc(text, screenWidth div 2, screenHeight div 2)
|
||||
|
||||
|
||||
## ## Matchmaking
|
||||
## To find an opponent, we will broadcast our address on a
|
||||
## GossipSub topic, and wait for someone to connect to us.
|
||||
## We will also listen to that topic, and connect to anyone
|
||||
## broadcasting his address.
|
||||
##
|
||||
## If we are looking for a game, we'll send `ok` to let the
|
||||
## peer know that we are available, check that he is also available,
|
||||
## and launch the game.
|
||||
proc new(T: typedesc[GameProto], g: Game): T =
|
||||
proc handle(conn: Connection, proto: string) {.async.} =
|
||||
defer: await conn.closeWithEof()
|
||||
if g.peerFound.finished or g.hasCandidate:
|
||||
await conn.close()
|
||||
return
|
||||
g.hasCandidate = true
|
||||
await conn.writeLp("ok")
|
||||
if "ok" != string.fromBytes(await conn.readLp(1024)):
|
||||
g.hasCandidate = false
|
||||
return
|
||||
g.peerFound.complete(conn)
|
||||
# The handler of a protocol must wait for the stream to
|
||||
# be finished before returning
|
||||
await conn.join()
|
||||
return T.new(codecs = @["/tron/1.0.0"], handler = handle)
|
||||
|
||||
proc networking(g: Game) {.async.} =
|
||||
# Create our switch, similar to the GossipSub example and
|
||||
# the Discovery examples combined
|
||||
let
|
||||
rdv = RendezVous.new()
|
||||
switch = SwitchBuilder.new()
|
||||
.withRng(newRng())
|
||||
.withAddresses(@[ MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet() ])
|
||||
.withTcpTransport()
|
||||
.withYamux()
|
||||
.withNoise()
|
||||
.withRendezVous(rdv)
|
||||
.build()
|
||||
dm = DiscoveryManager()
|
||||
gameProto = GameProto.new(g)
|
||||
gossip = GossipSub.init(
|
||||
switch = switch,
|
||||
triggerSelf = false)
|
||||
dm.add(RendezVousInterface.new(rdv))
|
||||
|
||||
switch.mount(gossip)
|
||||
switch.mount(gameProto)
|
||||
|
||||
gossip.subscribe(
|
||||
"/tron/matchmaking",
|
||||
proc (topic: string, data: seq[byte]) {.async.} =
|
||||
# If we are still looking for an opponent,
|
||||
# try to match anyone broadcasting it's address
|
||||
if g.peerFound.finished or g.hasCandidate: return
|
||||
g.hasCandidate = true
|
||||
|
||||
try:
|
||||
let
|
||||
(peerId, multiAddress) = parseFullAddress(data).tryGet()
|
||||
stream = await switch.dial(peerId, @[multiAddress], gameProto.codec)
|
||||
|
||||
await stream.writeLp("ok")
|
||||
if (await stream.readLp(10)) != "ok".toBytes:
|
||||
g.hasCandidate = false
|
||||
return
|
||||
g.peerFound.complete(stream)
|
||||
# We are "player 2"
|
||||
swap(g.localPlayer, g.remotePlayer)
|
||||
except CatchableError as exc:
|
||||
discard
|
||||
)
|
||||
|
||||
await switch.start()
|
||||
defer: await switch.stop()
|
||||
|
||||
# As explained in the last tutorial, we need a bootnode to be able
|
||||
# to find peers. We could use any libp2p running rendezvous (or any
|
||||
# node running tron). We will take it's MultiAddress from the command
|
||||
# line parameters
|
||||
if paramCount() > 0:
|
||||
let (peerId, multiAddress) = paramStr(1).parseFullAddress().tryGet()
|
||||
await switch.connect(peerId, @[multiAddress])
|
||||
else:
|
||||
echo "No bootnode provided, listening on: ", switch.peerInfo.fullAddrs.tryGet()
|
||||
|
||||
# Discover peers from the bootnode, and connect to them
|
||||
dm.advertise(RdvNamespace("tron"))
|
||||
let discoveryQuery = dm.request(RdvNamespace("tron"))
|
||||
discoveryQuery.forEach:
|
||||
try:
|
||||
await switch.connect(peer[PeerId], peer.getAll(MultiAddress))
|
||||
except CatchableError as exc:
|
||||
echo "Failed to dial a peer: ", exc.msg
|
||||
|
||||
# We will try to publish our address multiple times, in case
|
||||
# it takes time to establish connections with other GossipSub peers
|
||||
var published = false
|
||||
while not published:
|
||||
await sleepAsync(500.milliseconds)
|
||||
for fullAddr in switch.peerInfo.fullAddrs.tryGet():
|
||||
if (await gossip.publish("/tron/matchmaking", fullAddr.bytes)) == 0:
|
||||
published = false
|
||||
break
|
||||
published = true
|
||||
|
||||
discoveryQuery.stop()
|
||||
|
||||
# We now wait for someone to connect to us (or for us to connect to someone)
|
||||
let peerConn = await g.peerFound
|
||||
defer: await peerConn.closeWithEof()
|
||||
|
||||
await g.mainLoop(peerConn)
|
||||
|
||||
let
|
||||
game = Game.new()
|
||||
netFut = networking(game)
|
||||
nico.init("Status", "Tron")
|
||||
nico.createWindow("Tron", mapSize * 4, mapSize * 4, 4, false)
|
||||
nico.run(proc = discard, proc(dt: float32) = game.update(dt), proc = game.draw())
|
||||
waitFor(netFut.cancelAndWait())
|
||||
|
||||
## And that's it! If you want to run this code locally, the simplest way is to use the
|
||||
## first node as a boot node for the second one. But you can also use any rendezvous node
|
||||
103
libp2p.nim
103
libp2p.nim
@@ -1,40 +1,69 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
import
|
||||
libp2p/[protobuf/minprotobuf,
|
||||
muxers/muxer,
|
||||
muxers/mplex/mplex,
|
||||
stream/lpstream,
|
||||
stream/bufferstream,
|
||||
stream/connection,
|
||||
transports/transport,
|
||||
transports/tcptransport,
|
||||
protocols/secure/noise,
|
||||
cid,
|
||||
multihash,
|
||||
multibase,
|
||||
multicodec,
|
||||
errors,
|
||||
switch,
|
||||
peerid,
|
||||
peerinfo,
|
||||
multiaddress,
|
||||
builders,
|
||||
crypto/crypto,
|
||||
protocols/pubsub]
|
||||
when defined(nimdoc):
|
||||
## Welcome to the nim-libp2p reference!
|
||||
##
|
||||
## On the left, you'll find a switch that allows you to see private
|
||||
## procedures. By default, you'll only see the public one (marked with `{.public.}`)
|
||||
##
|
||||
## The difference between public and private procedures is that public procedure
|
||||
## stay backward compatible during the Major version, whereas private ones can
|
||||
## change at each new Minor version.
|
||||
##
|
||||
## If you're new to nim-libp2p, you can find a tutorial `here<https://status-im.github.io/nim-libp2p/docs/tutorial_1_connect/>`_
|
||||
## that can help you get started.
|
||||
|
||||
import bearssl
|
||||
# Import stuff for doc
|
||||
import libp2p/[
|
||||
protobuf/minprotobuf,
|
||||
switch,
|
||||
stream/lpstream,
|
||||
builders,
|
||||
transports/tcptransport,
|
||||
transports/wstransport,
|
||||
protocols/ping,
|
||||
protocols/pubsub,
|
||||
peerid,
|
||||
peerinfo,
|
||||
peerstore,
|
||||
multiaddress]
|
||||
|
||||
export
|
||||
minprotobuf, switch, peerid, peerinfo,
|
||||
connection, multiaddress, crypto, lpstream,
|
||||
bufferstream, bearssl, muxer, mplex, transport,
|
||||
tcptransport, noise, errors, cid, multihash,
|
||||
multicodec, builders, pubsub
|
||||
proc dummyPrivateProc*() =
|
||||
## A private proc example
|
||||
discard
|
||||
else:
|
||||
import
|
||||
libp2p/[protobuf/minprotobuf,
|
||||
muxers/muxer,
|
||||
muxers/mplex/mplex,
|
||||
stream/lpstream,
|
||||
stream/bufferstream,
|
||||
stream/connection,
|
||||
transports/transport,
|
||||
transports/tcptransport,
|
||||
protocols/secure/noise,
|
||||
cid,
|
||||
multihash,
|
||||
multicodec,
|
||||
errors,
|
||||
switch,
|
||||
peerid,
|
||||
peerinfo,
|
||||
multiaddress,
|
||||
builders,
|
||||
crypto/crypto,
|
||||
protocols/pubsub]
|
||||
|
||||
export
|
||||
minprotobuf, switch, peerid, peerinfo,
|
||||
connection, multiaddress, crypto, lpstream,
|
||||
bufferstream, muxer, mplex, transport,
|
||||
tcptransport, noise, errors, cid, multihash,
|
||||
multicodec, builders, pubsub
|
||||
|
||||
@@ -1,57 +1,59 @@
|
||||
mode = ScriptMode.Verbose
|
||||
|
||||
packageName = "libp2p"
|
||||
version = "0.0.2"
|
||||
version = "1.1.0"
|
||||
author = "Status Research & Development GmbH"
|
||||
description = "LibP2P implementation"
|
||||
license = "MIT"
|
||||
skipDirs = @["tests", "examples", "Nim", "tools", "scripts", "docs"]
|
||||
|
||||
requires "nim >= 1.2.0",
|
||||
requires "nim >= 1.6.0",
|
||||
"nimcrypto >= 0.4.1",
|
||||
"dnsclient >= 0.1.2",
|
||||
"dnsclient >= 0.3.0 & < 0.4.0",
|
||||
"bearssl >= 0.1.4",
|
||||
"chronicles >= 0.10.2",
|
||||
"chronos >= 3.0.6",
|
||||
"chronos >= 4.0.0",
|
||||
"metrics",
|
||||
"secp256k1",
|
||||
"stew#head",
|
||||
"websock"
|
||||
"websock",
|
||||
"unittest2"
|
||||
|
||||
const nimflags =
|
||||
"--verbosity:0 --hints:off " &
|
||||
"--warning[CaseTransition]:off --warning[ObservableStores]:off " &
|
||||
"--warning[LockLevel]:off " &
|
||||
"-d:chronosStrictException " &
|
||||
"--styleCheck:usages --styleCheck:hint "
|
||||
let nimc = getEnv("NIMC", "nim") # Which nim compiler to use
|
||||
let lang = getEnv("NIMLANG", "c") # Which backend (c/cpp/js)
|
||||
let flags = getEnv("NIMFLAGS", "") # Extra flags for the compiler
|
||||
let verbose = getEnv("V", "") notin ["", "0"]
|
||||
|
||||
let cfg =
|
||||
" --styleCheck:usages --styleCheck:error" &
|
||||
(if verbose: "" else: " --verbosity:0 --hints:off") &
|
||||
" --skipParentCfg --skipUserCfg -f" &
|
||||
" --threads:on --opt:speed"
|
||||
|
||||
import hashes, strutils
|
||||
|
||||
proc runTest(filename: string, verify: bool = true, sign: bool = true,
|
||||
moreoptions: string = "") =
|
||||
var excstr = "nim c --opt:speed -d:debug -d:libp2p_agents_metrics -d:libp2p_protobuf_metrics -d:libp2p_network_protocols_metrics -d:libp2p_mplex_metrics "
|
||||
excstr.add(" " & getEnv("NIMFLAGS") & " ")
|
||||
excstr.add(" " & nimflags & " ")
|
||||
var excstr = nimc & " " & lang & " -d:debug " & cfg & " " & flags
|
||||
excstr.add(" -d:libp2p_pubsub_sign=" & $sign)
|
||||
excstr.add(" -d:libp2p_pubsub_verify=" & $verify)
|
||||
excstr.add(" " & moreoptions & " ")
|
||||
if verify and sign:
|
||||
# build it with TRACE and JSON logs
|
||||
exec excstr & " -d:chronicles_log_level=TRACE -d:chronicles_sinks:json" & " tests/" & filename
|
||||
# build it again, to run it with less verbose logs
|
||||
exec excstr & " -d:chronicles_log_level=INFO -r" & " tests/" & filename
|
||||
if getEnv("CICOV").len > 0:
|
||||
excstr &= " --nimcache:nimcache/" & filename & "-" & $excstr.hash
|
||||
exec excstr & " -r " & " tests/" & filename
|
||||
rmFile "tests/" & filename.toExe
|
||||
|
||||
proc buildSample(filename: string, run = false) =
|
||||
var excstr = "nim c --opt:speed --threads:on -d:debug "
|
||||
excstr.add(" " & nimflags & " ")
|
||||
proc buildSample(filename: string, run = false, extraFlags = "") =
|
||||
var excstr = nimc & " " & lang & " " & cfg & " " & flags & " -p:. " & extraFlags
|
||||
excstr.add(" examples/" & filename)
|
||||
exec excstr
|
||||
if run:
|
||||
exec "./examples/" & filename.toExe
|
||||
rmFile "examples/" & filename.toExe
|
||||
|
||||
proc buildTutorial(filename: string) =
|
||||
discard gorge "cat " & filename & " | nim c -r --hints:off tools/markdown_runner.nim | " &
|
||||
" nim " & nimflags & " c -"
|
||||
proc tutorialToMd(filename: string) =
|
||||
let markdown = gorge "cat " & filename & " | " & nimc & " " & lang & " -r --verbosity:0 --hints:off tools/markdown_builder.nim "
|
||||
writeFile(filename.replace(".nim", ".md"), markdown)
|
||||
|
||||
task testnative, "Runs libp2p native tests":
|
||||
runTest("testnative")
|
||||
@@ -96,11 +98,28 @@ task test_slim, "Runs the (slimmed down) test suite":
|
||||
exec "nimble testfilter"
|
||||
exec "nimble examples_build"
|
||||
|
||||
task website, "Build the website":
|
||||
tutorialToMd("examples/tutorial_1_connect.nim")
|
||||
tutorialToMd("examples/tutorial_2_customproto.nim")
|
||||
tutorialToMd("examples/tutorial_3_protobuf.nim")
|
||||
tutorialToMd("examples/tutorial_4_gossipsub.nim")
|
||||
tutorialToMd("examples/tutorial_5_discovery.nim")
|
||||
tutorialToMd("examples/tutorial_6_game.nim")
|
||||
tutorialToMd("examples/circuitrelay.nim")
|
||||
exec "mkdocs build"
|
||||
|
||||
task examples_build, "Build the samples":
|
||||
buildSample("directchat")
|
||||
buildSample("helloworld", true)
|
||||
buildTutorial("examples/tutorial_1_connect.md")
|
||||
buildTutorial("examples/tutorial_2_customproto.md")
|
||||
buildSample("circuitrelay", true)
|
||||
buildSample("tutorial_1_connect", true)
|
||||
buildSample("tutorial_2_customproto", true)
|
||||
buildSample("tutorial_3_protobuf", true)
|
||||
buildSample("tutorial_4_gossipsub", true)
|
||||
buildSample("tutorial_5_discovery", true)
|
||||
exec "nimble install -y nimpng@#HEAD" # this is to fix broken build on 1.7.3, remove it when nimpng version 0.3.2 or later is released
|
||||
exec "nimble install -y nico"
|
||||
buildSample("tutorial_6_game", false, "--styleCheck:off")
|
||||
|
||||
# pin system
|
||||
# while nimble lockfile
|
||||
@@ -111,7 +130,7 @@ task pin, "Create a lockfile":
|
||||
# pinner.nim was originally here
|
||||
# but you can't read output from
|
||||
# a command in a nimscript
|
||||
exec "nim c -r tools/pinner.nim"
|
||||
exec nimc & " c -r tools/pinner.nim"
|
||||
|
||||
import sequtils
|
||||
import os
|
||||
@@ -125,10 +144,24 @@ task install_pinned, "Reads the lockfile":
|
||||
|
||||
# Remove the automatically installed deps
|
||||
# (inefficient you say?)
|
||||
let allowedDirectories = toInstall.mapIt(it[0] & "-" & it[1].split('@')[1])
|
||||
for dependency in listDirs("nimbledeps/pkgs"):
|
||||
if dependency.extractFilename notin allowedDirectories:
|
||||
rmDir(dependency)
|
||||
let nimblePkgs =
|
||||
if system.dirExists("nimbledeps/pkgs"): "nimbledeps/pkgs"
|
||||
else: "nimbledeps/pkgs2"
|
||||
for dependency in listDirs(nimblePkgs):
|
||||
let
|
||||
fileName = dependency.extractFilename
|
||||
fileContent = readFile(dependency & "/nimblemeta.json")
|
||||
packageName = fileName.split('-')[0]
|
||||
|
||||
if toInstall.anyIt(
|
||||
it[0] == packageName and
|
||||
(
|
||||
it[1].split('#')[^1] in fileContent or # nimble for nim 2.X
|
||||
fileName.endsWith(it[1].split('#')[^1]) # nimble for nim 1.X
|
||||
)
|
||||
) == false or
|
||||
fileName.split('-')[^1].len < 20: # safegard for nimble for nim 1.X
|
||||
rmDir(dependency)
|
||||
|
||||
task unpin, "Restore global package use":
|
||||
rmDir("nimbledeps")
|
||||
|
||||
@@ -1,45 +1,50 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2020 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
## This module contains a Switch Building helper.
|
||||
runnableExamples:
|
||||
let switch =
|
||||
SwitchBuilder.new()
|
||||
.withRng(rng)
|
||||
.withAddresses(multiaddress)
|
||||
# etc
|
||||
.build()
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
options, tables, chronos, chronicles, bearssl,
|
||||
options, tables, chronos, chronicles, sequtils,
|
||||
switch, peerid, peerinfo, stream/connection, multiaddress,
|
||||
crypto/crypto, transports/[transport, tcptransport],
|
||||
muxers/[muxer, mplex/mplex],
|
||||
protocols/[identify, secure/secure, secure/noise, relay],
|
||||
connmanager, upgrademngrs/muxedupgrade,
|
||||
muxers/[muxer, mplex/mplex, yamux/yamux],
|
||||
protocols/[identify, secure/secure, secure/noise, rendezvous],
|
||||
protocols/connectivity/[autonat/server, relay/relay, relay/client, relay/rtransport],
|
||||
connmanager, upgrademngrs/muxedupgrade, observedaddrmanager,
|
||||
nameresolving/nameresolver,
|
||||
errors
|
||||
errors, utility
|
||||
|
||||
export
|
||||
switch, peerid, peerinfo, connection, multiaddress, crypto, errors
|
||||
|
||||
type
|
||||
TransportProvider* = proc(upgr: Upgrade): Transport {.gcsafe, raises: [Defect].}
|
||||
TransportProvider* {.public.} = proc(upgr: Upgrade): Transport {.gcsafe, raises: [].}
|
||||
|
||||
SecureProtocol* {.pure.} = enum
|
||||
Noise,
|
||||
Secio {.deprecated.}
|
||||
|
||||
MplexOpts = object
|
||||
enable: bool
|
||||
newMuxer: MuxerConstructor
|
||||
Noise
|
||||
|
||||
SwitchBuilder* = ref object
|
||||
privKey: Option[PrivateKey]
|
||||
addresses: seq[MultiAddress]
|
||||
secureManagers: seq[SecureProtocol]
|
||||
mplexOpts: MplexOpts
|
||||
muxers: seq[MuxerProvider]
|
||||
transports: seq[TransportProvider]
|
||||
rng: ref BrHmacDrbgContext
|
||||
rng: ref HmacDrbgContext
|
||||
maxConnections: int
|
||||
maxIn: int
|
||||
sendSignedPeerRecord: bool
|
||||
@@ -48,11 +53,15 @@ type
|
||||
protoVersion: string
|
||||
agentVersion: string
|
||||
nameResolver: NameResolver
|
||||
peerStoreCapacity: Option[int]
|
||||
isCircuitRelay: bool
|
||||
circuitRelayCanHop: bool
|
||||
peerStoreCapacity: Opt[int]
|
||||
autonat: bool
|
||||
circuitRelay: Relay
|
||||
rdv: RendezVous
|
||||
services: seq[Service]
|
||||
observedAddrManager: ObservedAddrManager
|
||||
|
||||
proc new*(T: type[SwitchBuilder]): T =
|
||||
proc new*(T: type[SwitchBuilder]): T {.public.} =
|
||||
## Creates a SwitchBuilder
|
||||
|
||||
let address = MultiAddress
|
||||
.init("/ip4/127.0.0.1/tcp/0")
|
||||
@@ -67,93 +76,141 @@ proc new*(T: type[SwitchBuilder]): T =
|
||||
maxOut: -1,
|
||||
maxConnsPerPeer: MaxConnectionsPerPeer,
|
||||
protoVersion: ProtoVersion,
|
||||
agentVersion: AgentVersion,
|
||||
isCircuitRelay: false)
|
||||
agentVersion: AgentVersion)
|
||||
|
||||
proc withPrivateKey*(b: SwitchBuilder, privateKey: PrivateKey): SwitchBuilder {.public.} =
|
||||
## Set the private key of the switch. Will be used to
|
||||
## generate a PeerId
|
||||
|
||||
proc withPrivateKey*(b: SwitchBuilder, privateKey: PrivateKey): SwitchBuilder =
|
||||
b.privKey = some(privateKey)
|
||||
b
|
||||
|
||||
proc withAddress*(b: SwitchBuilder, address: MultiAddress): SwitchBuilder =
|
||||
proc withAddress*(b: SwitchBuilder, address: MultiAddress): SwitchBuilder {.public.} =
|
||||
## | Set the listening address of the switch
|
||||
## | Calling it multiple time will override the value
|
||||
|
||||
b.addresses = @[address]
|
||||
b
|
||||
|
||||
proc withAddresses*(b: SwitchBuilder, addresses: seq[MultiAddress]): SwitchBuilder =
|
||||
proc withAddresses*(b: SwitchBuilder, addresses: seq[MultiAddress]): SwitchBuilder {.public.} =
|
||||
## | Set the listening addresses of the switch
|
||||
## | Calling it multiple time will override the value
|
||||
|
||||
b.addresses = addresses
|
||||
b
|
||||
|
||||
proc withSignedPeerRecord*(b: SwitchBuilder, sendIt = true): SwitchBuilder =
|
||||
proc withSignedPeerRecord*(b: SwitchBuilder, sendIt = true): SwitchBuilder {.public.} =
|
||||
b.sendSignedPeerRecord = sendIt
|
||||
b
|
||||
|
||||
proc withMplex*(b: SwitchBuilder, inTimeout = 5.minutes, outTimeout = 5.minutes): SwitchBuilder =
|
||||
proc withMplex*(
|
||||
b: SwitchBuilder,
|
||||
inTimeout = 5.minutes,
|
||||
outTimeout = 5.minutes,
|
||||
maxChannCount = 200): SwitchBuilder {.public.} =
|
||||
## | Uses `Mplex <https://docs.libp2p.io/concepts/stream-multiplexing/#mplex>`_ as a multiplexer
|
||||
## | `Timeout` is the duration after which a inactive connection will be closed
|
||||
proc newMuxer(conn: Connection): Muxer =
|
||||
Mplex.new(
|
||||
conn,
|
||||
inTimeout = inTimeout,
|
||||
outTimeout = outTimeout)
|
||||
|
||||
b.mplexOpts = MplexOpts(
|
||||
enable: true,
|
||||
newMuxer: newMuxer,
|
||||
)
|
||||
inTimeout,
|
||||
outTimeout,
|
||||
maxChannCount)
|
||||
|
||||
assert b.muxers.countIt(it.codec == MplexCodec) == 0, "Mplex build multiple times"
|
||||
b.muxers.add(MuxerProvider.new(newMuxer, MplexCodec))
|
||||
b
|
||||
|
||||
proc withNoise*(b: SwitchBuilder): SwitchBuilder =
|
||||
proc withYamux*(b: SwitchBuilder,
|
||||
windowSize: int = YamuxDefaultWindowSize,
|
||||
inTimeout: Duration = 5.minutes,
|
||||
outTimeout: Duration = 5.minutes): SwitchBuilder =
|
||||
proc newMuxer(conn: Connection): Muxer =
|
||||
Yamux.new(conn, windowSize, inTimeout = inTimeout, outTimeout = outTimeout)
|
||||
|
||||
assert b.muxers.countIt(it.codec == YamuxCodec) == 0, "Yamux build multiple times"
|
||||
b.muxers.add(MuxerProvider.new(newMuxer, YamuxCodec))
|
||||
b
|
||||
|
||||
proc withNoise*(b: SwitchBuilder): SwitchBuilder {.public.} =
|
||||
b.secureManagers.add(SecureProtocol.Noise)
|
||||
b
|
||||
|
||||
proc withTransport*(b: SwitchBuilder, prov: TransportProvider): SwitchBuilder =
|
||||
proc withTransport*(b: SwitchBuilder, prov: TransportProvider): SwitchBuilder {.public.} =
|
||||
## Use a custom transport
|
||||
runnableExamples:
|
||||
let switch =
|
||||
SwitchBuilder.new()
|
||||
.withTransport(proc(upgr: Upgrade): Transport = TcpTransport.new(flags, upgr))
|
||||
.build()
|
||||
b.transports.add(prov)
|
||||
b
|
||||
|
||||
proc withTcpTransport*(b: SwitchBuilder, flags: set[ServerFlags] = {}): SwitchBuilder =
|
||||
proc withTcpTransport*(b: SwitchBuilder, flags: set[ServerFlags] = {}): SwitchBuilder {.public.} =
|
||||
b.withTransport(proc(upgr: Upgrade): Transport = TcpTransport.new(flags, upgr))
|
||||
|
||||
proc withRng*(b: SwitchBuilder, rng: ref BrHmacDrbgContext): SwitchBuilder =
|
||||
proc withRng*(b: SwitchBuilder, rng: ref HmacDrbgContext): SwitchBuilder {.public.} =
|
||||
b.rng = rng
|
||||
b
|
||||
|
||||
proc withMaxConnections*(b: SwitchBuilder, maxConnections: int): SwitchBuilder =
|
||||
proc withMaxConnections*(b: SwitchBuilder, maxConnections: int): SwitchBuilder {.public.} =
|
||||
## Maximum concurrent connections of the switch. You should either use this, or
|
||||
## `withMaxIn <#withMaxIn,SwitchBuilder,int>`_ & `withMaxOut<#withMaxOut,SwitchBuilder,int>`_
|
||||
b.maxConnections = maxConnections
|
||||
b
|
||||
|
||||
proc withMaxIn*(b: SwitchBuilder, maxIn: int): SwitchBuilder =
|
||||
proc withMaxIn*(b: SwitchBuilder, maxIn: int): SwitchBuilder {.public.} =
|
||||
## Maximum concurrent incoming connections. Should be used with `withMaxOut<#withMaxOut,SwitchBuilder,int>`_
|
||||
b.maxIn = maxIn
|
||||
b
|
||||
|
||||
proc withMaxOut*(b: SwitchBuilder, maxOut: int): SwitchBuilder =
|
||||
proc withMaxOut*(b: SwitchBuilder, maxOut: int): SwitchBuilder {.public.} =
|
||||
## Maximum concurrent outgoing connections. Should be used with `withMaxIn<#withMaxIn,SwitchBuilder,int>`_
|
||||
b.maxOut = maxOut
|
||||
b
|
||||
|
||||
proc withMaxConnsPerPeer*(b: SwitchBuilder, maxConnsPerPeer: int): SwitchBuilder =
|
||||
proc withMaxConnsPerPeer*(b: SwitchBuilder, maxConnsPerPeer: int): SwitchBuilder {.public.} =
|
||||
b.maxConnsPerPeer = maxConnsPerPeer
|
||||
b
|
||||
|
||||
proc withPeerStore*(b: SwitchBuilder, capacity: int): SwitchBuilder =
|
||||
b.peerStoreCapacity = some(capacity)
|
||||
proc withPeerStore*(b: SwitchBuilder, capacity: int): SwitchBuilder {.public.} =
|
||||
b.peerStoreCapacity = Opt.some(capacity)
|
||||
b
|
||||
|
||||
proc withProtoVersion*(b: SwitchBuilder, protoVersion: string): SwitchBuilder =
|
||||
proc withProtoVersion*(b: SwitchBuilder, protoVersion: string): SwitchBuilder {.public.} =
|
||||
b.protoVersion = protoVersion
|
||||
b
|
||||
|
||||
proc withAgentVersion*(b: SwitchBuilder, agentVersion: string): SwitchBuilder =
|
||||
proc withAgentVersion*(b: SwitchBuilder, agentVersion: string): SwitchBuilder {.public.} =
|
||||
b.agentVersion = agentVersion
|
||||
b
|
||||
|
||||
proc withNameResolver*(b: SwitchBuilder, nameResolver: NameResolver): SwitchBuilder =
|
||||
proc withNameResolver*(b: SwitchBuilder, nameResolver: NameResolver): SwitchBuilder {.public.} =
|
||||
b.nameResolver = nameResolver
|
||||
b
|
||||
|
||||
proc withRelayTransport*(b: SwitchBuilder, canHop: bool): SwitchBuilder =
|
||||
b.isCircuitRelay = true
|
||||
b.circuitRelayCanHop = canHop
|
||||
proc withAutonat*(b: SwitchBuilder): SwitchBuilder =
|
||||
b.autonat = true
|
||||
b
|
||||
|
||||
proc withCircuitRelay*(b: SwitchBuilder, r: Relay = Relay.new()): SwitchBuilder =
|
||||
b.circuitRelay = r
|
||||
b
|
||||
|
||||
proc withRendezVous*(b: SwitchBuilder, rdv: RendezVous = RendezVous.new()): SwitchBuilder =
|
||||
b.rdv = rdv
|
||||
b
|
||||
|
||||
proc withServices*(b: SwitchBuilder, services: seq[Service]): SwitchBuilder =
|
||||
b.services = services
|
||||
b
|
||||
|
||||
proc withObservedAddrManager*(b: SwitchBuilder, observedAddrManager: ObservedAddrManager): SwitchBuilder =
|
||||
b.observedAddrManager = observedAddrManager
|
||||
b
|
||||
|
||||
proc build*(b: SwitchBuilder): Switch
|
||||
{.raises: [Defect, LPError].} =
|
||||
{.raises: [LPError], public.} =
|
||||
|
||||
if b.rng == nil: # newRng could fail
|
||||
raise newException(Defect, "Cannot initialize RNG")
|
||||
@@ -174,18 +231,16 @@ proc build*(b: SwitchBuilder): Switch
|
||||
protoVersion = b.protoVersion,
|
||||
agentVersion = b.agentVersion)
|
||||
|
||||
let
|
||||
muxers = block:
|
||||
var muxers: Table[string, MuxerProvider]
|
||||
if b.mplexOpts.enable:
|
||||
muxers[MplexCodec] = MuxerProvider.new(b.mplexOpts.newMuxer, MplexCodec)
|
||||
muxers
|
||||
let identify =
|
||||
if b.observedAddrManager != nil:
|
||||
Identify.new(peerInfo, b.sendSignedPeerRecord, b.observedAddrManager)
|
||||
else:
|
||||
Identify.new(peerInfo, b.sendSignedPeerRecord)
|
||||
|
||||
let
|
||||
identify = Identify.new(peerInfo, b.sendSignedPeerRecord)
|
||||
connManager = ConnManager.new(b.maxConnsPerPeer, b.maxConnections, b.maxIn, b.maxOut)
|
||||
ms = MultistreamSelect.new()
|
||||
muxedUpgrade = MuxedUpgrade.new(identify, muxers, secureManagerInstances, connManager, ms)
|
||||
muxedUpgrade = MuxedUpgrade.new(b.muxers, secureManagerInstances, ms)
|
||||
|
||||
let
|
||||
transports = block:
|
||||
@@ -200,51 +255,60 @@ proc build*(b: SwitchBuilder): Switch
|
||||
if isNil(b.rng):
|
||||
b.rng = newRng()
|
||||
|
||||
let peerStore =
|
||||
if isSome(b.peerStoreCapacity):
|
||||
PeerStore.new(b.peerStoreCapacity.get())
|
||||
let peerStore = block:
|
||||
b.peerStoreCapacity.withValue(capacity):
|
||||
PeerStore.new(identify, capacity)
|
||||
else:
|
||||
PeerStore.new()
|
||||
PeerStore.new(identify)
|
||||
|
||||
let switch = newSwitch(
|
||||
peerInfo = peerInfo,
|
||||
transports = transports,
|
||||
identity = identify,
|
||||
muxers = muxers,
|
||||
secureManagers = secureManagerInstances,
|
||||
connManager = connManager,
|
||||
ms = ms,
|
||||
nameResolver = b.nameResolver,
|
||||
peerStore = peerStore)
|
||||
peerStore = peerStore,
|
||||
services = b.services)
|
||||
|
||||
if b.isCircuitRelay:
|
||||
let relay = Relay.new(switch, b.circuitRelayCanHop)
|
||||
switch.mount(relay)
|
||||
switch.addTransport(RelayTransport.new(relay, muxedUpgrade))
|
||||
switch.mount(identify)
|
||||
|
||||
if b.autonat:
|
||||
let autonat = Autonat.new(switch)
|
||||
switch.mount(autonat)
|
||||
|
||||
if not isNil(b.circuitRelay):
|
||||
if b.circuitRelay of RelayClient:
|
||||
switch.addTransport(RelayTransport.new(RelayClient(b.circuitRelay), muxedUpgrade))
|
||||
b.circuitRelay.setup(switch)
|
||||
switch.mount(b.circuitRelay)
|
||||
|
||||
if not isNil(b.rdv):
|
||||
b.rdv.setup(switch)
|
||||
switch.mount(b.rdv)
|
||||
|
||||
return switch
|
||||
|
||||
proc newStandardSwitch*(
|
||||
privKey = none(PrivateKey),
|
||||
addrs: MultiAddress | seq[MultiAddress] = MultiAddress.init("/ip4/127.0.0.1/tcp/0").tryGet(),
|
||||
secureManagers: openArray[SecureProtocol] = [
|
||||
privKey = none(PrivateKey),
|
||||
addrs: MultiAddress | seq[MultiAddress] =
|
||||
MultiAddress.init("/ip4/127.0.0.1/tcp/0").expect("valid address"),
|
||||
secureManagers: openArray[SecureProtocol] = [
|
||||
SecureProtocol.Noise,
|
||||
],
|
||||
transportFlags: set[ServerFlags] = {},
|
||||
rng = newRng(),
|
||||
inTimeout: Duration = 5.minutes,
|
||||
outTimeout: Duration = 5.minutes,
|
||||
maxConnections = MaxConnections,
|
||||
maxIn = -1,
|
||||
maxOut = -1,
|
||||
maxConnsPerPeer = MaxConnectionsPerPeer,
|
||||
nameResolver: NameResolver = nil,
|
||||
sendSignedPeerRecord = false,
|
||||
peerStoreCapacity = 1000): Switch
|
||||
{.raises: [Defect, LPError].} =
|
||||
if SecureProtocol.Secio in secureManagers:
|
||||
quit("Secio is deprecated!") # use of secio is unsafe
|
||||
|
||||
transportFlags: set[ServerFlags] = {},
|
||||
rng = newRng(),
|
||||
inTimeout: Duration = 5.minutes,
|
||||
outTimeout: Duration = 5.minutes,
|
||||
maxConnections = MaxConnections,
|
||||
maxIn = -1,
|
||||
maxOut = -1,
|
||||
maxConnsPerPeer = MaxConnectionsPerPeer,
|
||||
nameResolver: NameResolver = nil,
|
||||
sendSignedPeerRecord = false,
|
||||
peerStoreCapacity = 1000
|
||||
): Switch {.raises: [LPError], public.} =
|
||||
## Helper for common switch configurations.
|
||||
let addrs = when addrs is MultiAddress: @[addrs] else: addrs
|
||||
var b = SwitchBuilder
|
||||
.new()
|
||||
@@ -261,7 +325,7 @@ proc newStandardSwitch*(
|
||||
.withNameResolver(nameResolver)
|
||||
.withNoise()
|
||||
|
||||
if privKey.isSome():
|
||||
b = b.withPrivateKey(privKey.get())
|
||||
privKey.withValue(pkey):
|
||||
b = b.withPrivateKey(pkey)
|
||||
|
||||
b.build()
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implementes CID (Content IDentifier).
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import tables, hashes
|
||||
import multibase, multicodec, multihash, vbuffer, varint
|
||||
@@ -261,12 +261,6 @@ proc write*(vb: var VBuffer, cid: Cid) {.inline.} =
|
||||
## Write CID value ``cid`` to buffer ``vb``.
|
||||
vb.writeArray(cid.data.buffer)
|
||||
|
||||
proc encode*(mbtype: typedesc[MultiBase], encoding: string,
|
||||
cid: Cid): string {.inline.} =
|
||||
## Get MultiBase encoded representation of ``cid`` using encoding
|
||||
## ``encoding``.
|
||||
result = MultiBase.encode(encoding, cid.data.buffer).tryGet()
|
||||
|
||||
proc hash*(cid: Cid): Hash {.inline.} =
|
||||
hash(cid.data.buffer)
|
||||
|
||||
@@ -276,9 +270,6 @@ proc `$`*(cid: Cid): string =
|
||||
BTCBase58.encode(cid.data.buffer)
|
||||
elif cid.cidver == CIDv1:
|
||||
let res = MultiBase.encode("base58btc", cid.data.buffer)
|
||||
if res.isOk():
|
||||
res.get()
|
||||
else:
|
||||
""
|
||||
res.get("")
|
||||
else:
|
||||
""
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2020 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[options, tables, sequtils, sets]
|
||||
import std/[tables, sequtils, sets]
|
||||
import pkg/[chronos, chronicles, metrics]
|
||||
import peerinfo,
|
||||
peerstore,
|
||||
@@ -29,9 +29,7 @@ const
|
||||
|
||||
type
|
||||
TooManyConnectionsError* = object of LPError
|
||||
|
||||
ConnProvider* = proc(): Future[Connection]
|
||||
{.gcsafe, closure, raises: [Defect].}
|
||||
AlreadyExpectingConnectionError* = object of LPError
|
||||
|
||||
ConnEventKind* {.pure.} = enum
|
||||
Connected, # A connection was made and securely upgraded - there may be
|
||||
@@ -50,11 +48,10 @@ type
|
||||
|
||||
ConnEventHandler* =
|
||||
proc(peerId: PeerId, event: ConnEvent): Future[void]
|
||||
{.gcsafe, raises: [Defect].}
|
||||
{.gcsafe, raises: [].}
|
||||
|
||||
PeerEventKind* {.pure.} = enum
|
||||
Left,
|
||||
Identified,
|
||||
Joined
|
||||
|
||||
PeerEvent* = object
|
||||
@@ -65,22 +62,22 @@ type
|
||||
discard
|
||||
|
||||
PeerEventHandler* =
|
||||
proc(peerId: PeerId, event: PeerEvent): Future[void] {.gcsafe, raises: [Defect].}
|
||||
|
||||
MuxerHolder = object
|
||||
muxer: Muxer
|
||||
handle: Future[void]
|
||||
proc(peerId: PeerId, event: PeerEvent): Future[void] {.gcsafe, raises: [].}
|
||||
|
||||
ConnManager* = ref object of RootObj
|
||||
maxConnsPerPeer: int
|
||||
inSema*: AsyncSemaphore
|
||||
outSema*: AsyncSemaphore
|
||||
conns: Table[PeerId, HashSet[Connection]]
|
||||
muxed: Table[Connection, MuxerHolder]
|
||||
muxed: Table[PeerId, seq[Muxer]]
|
||||
connEvents: array[ConnEventKind, OrderedSet[ConnEventHandler]]
|
||||
peerEvents: array[PeerEventKind, OrderedSet[PeerEventHandler]]
|
||||
expectedConnectionsOverLimit*: Table[(PeerId, Direction), Future[Muxer]]
|
||||
peerStore*: PeerStore
|
||||
|
||||
ConnectionSlot* = object
|
||||
connManager: ConnManager
|
||||
direction: Direction
|
||||
|
||||
proc newTooManyConnectionsError(): ref TooManyConnectionsError {.inline.} =
|
||||
result = newException(TooManyConnectionsError, "Too many connections")
|
||||
|
||||
@@ -104,39 +101,34 @@ proc new*(C: type ConnManager,
|
||||
outSema: outSema)
|
||||
|
||||
proc connCount*(c: ConnManager, peerId: PeerId): int =
|
||||
c.conns.getOrDefault(peerId).len
|
||||
c.muxed.getOrDefault(peerId).len
|
||||
|
||||
proc connectedPeers*(c: ConnManager, dir: Direction): seq[PeerId] =
|
||||
var peers = newSeq[PeerId]()
|
||||
for peerId, mux in c.muxed:
|
||||
if mux.anyIt(it.connection.dir == dir):
|
||||
peers.add(peerId)
|
||||
return peers
|
||||
|
||||
proc getConnections*(c: ConnManager): Table[PeerId, seq[Muxer]] =
|
||||
return c.muxed
|
||||
|
||||
proc addConnEventHandler*(c: ConnManager,
|
||||
handler: ConnEventHandler,
|
||||
kind: ConnEventKind) =
|
||||
## Add peer event handler - handlers must not raise exceptions!
|
||||
##
|
||||
|
||||
try:
|
||||
if isNil(handler): return
|
||||
c.connEvents[kind].incl(handler)
|
||||
except Exception as exc:
|
||||
# TODO: there is an Exception being raised
|
||||
# somewhere in the depths of the std.
|
||||
# Might be related to https://github.com/nim-lang/Nim/issues/17382
|
||||
|
||||
raiseAssert exc.msg
|
||||
if isNil(handler): return
|
||||
c.connEvents[kind].incl(handler)
|
||||
|
||||
proc removeConnEventHandler*(c: ConnManager,
|
||||
handler: ConnEventHandler,
|
||||
kind: ConnEventKind) =
|
||||
try:
|
||||
c.connEvents[kind].excl(handler)
|
||||
except Exception as exc:
|
||||
# TODO: there is an Exception being raised
|
||||
# somewhere in the depths of the std.
|
||||
# Might be related to https://github.com/nim-lang/Nim/issues/17382
|
||||
|
||||
raiseAssert exc.msg
|
||||
|
||||
proc triggerConnEvent*(c: ConnManager,
|
||||
peerId: PeerId,
|
||||
event: ConnEvent) {.async, gcsafe.} =
|
||||
event: ConnEvent) {.async.} =
|
||||
try:
|
||||
trace "About to trigger connection events", peer = peerId
|
||||
if c.connEvents[event.kind].len() > 0:
|
||||
@@ -159,44 +151,22 @@ proc addPeerEventHandler*(c: ConnManager,
|
||||
##
|
||||
|
||||
if isNil(handler): return
|
||||
try:
|
||||
c.peerEvents[kind].incl(handler)
|
||||
except Exception as exc:
|
||||
# TODO: there is an Exception being raised
|
||||
# somewhere in the depths of the std.
|
||||
# Might be related to https://github.com/nim-lang/Nim/issues/17382
|
||||
|
||||
raiseAssert exc.msg
|
||||
c.peerEvents[kind].incl(handler)
|
||||
|
||||
proc removePeerEventHandler*(c: ConnManager,
|
||||
handler: PeerEventHandler,
|
||||
kind: PeerEventKind) =
|
||||
try:
|
||||
c.peerEvents[kind].excl(handler)
|
||||
except Exception as exc:
|
||||
# TODO: there is an Exception being raised
|
||||
# somewhere in the depths of the std.
|
||||
# Might be related to https://github.com/nim-lang/Nim/issues/17382
|
||||
|
||||
raiseAssert exc.msg
|
||||
c.peerEvents[kind].excl(handler)
|
||||
|
||||
proc triggerPeerEvents*(c: ConnManager,
|
||||
peerId: PeerId,
|
||||
event: PeerEvent) {.async, gcsafe.} =
|
||||
event: PeerEvent) {.async.} =
|
||||
|
||||
trace "About to trigger peer events", peer = peerId
|
||||
if c.peerEvents[event.kind].len == 0:
|
||||
return
|
||||
|
||||
try:
|
||||
let count = c.connCount(peerId)
|
||||
if event.kind == PeerEventKind.Joined and count != 1:
|
||||
trace "peer already joined", peer = peerId, event = $event
|
||||
return
|
||||
elif event.kind == PeerEventKind.Left and count != 0:
|
||||
trace "peer still connected or already left", peer = peerId, event = $event
|
||||
return
|
||||
|
||||
trace "triggering peer events", peer = peerId, event = $event
|
||||
|
||||
var peerEvents: seq[Future[void]]
|
||||
@@ -209,18 +179,22 @@ proc triggerPeerEvents*(c: ConnManager,
|
||||
except CatchableError as exc: # handlers should not raise!
|
||||
warn "Exception in triggerPeerEvents", exc = exc.msg, peer = peerId
|
||||
|
||||
proc contains*(c: ConnManager, conn: Connection): bool =
|
||||
## checks if a connection is being tracked by the
|
||||
## connection manager
|
||||
##
|
||||
proc expectConnection*(c: ConnManager, p: PeerId, dir: Direction): Future[Muxer] {.async.} =
|
||||
## Wait for a peer to connect to us. This will bypass the `MaxConnectionsPerPeer`
|
||||
let key = (p, dir)
|
||||
if key in c.expectedConnectionsOverLimit:
|
||||
raise newException(AlreadyExpectingConnectionError, "Already expecting an incoming connection from that peer")
|
||||
|
||||
if isNil(conn):
|
||||
return
|
||||
let future = newFuture[Muxer]()
|
||||
c.expectedConnectionsOverLimit[key] = future
|
||||
|
||||
return conn in c.conns.getOrDefault(conn.peerId)
|
||||
try:
|
||||
return await future
|
||||
finally:
|
||||
c.expectedConnectionsOverLimit.del(key)
|
||||
|
||||
proc contains*(c: ConnManager, peerId: PeerId): bool =
|
||||
peerId in c.conns
|
||||
peerId in c.muxed
|
||||
|
||||
proc contains*(c: ConnManager, muxer: Muxer): bool =
|
||||
## checks if a muxer is being tracked by the connection
|
||||
@@ -228,339 +202,216 @@ proc contains*(c: ConnManager, muxer: Muxer): bool =
|
||||
##
|
||||
|
||||
if isNil(muxer):
|
||||
return
|
||||
return false
|
||||
|
||||
let conn = muxer.connection
|
||||
if conn notin c:
|
||||
return
|
||||
return muxer in c.muxed.getOrDefault(conn.peerId)
|
||||
|
||||
if conn notin c.muxed:
|
||||
return
|
||||
proc closeMuxer(muxer: Muxer) {.async.} =
|
||||
trace "Cleaning up muxer", m = muxer
|
||||
|
||||
return muxer == c.muxed.getOrDefault(conn).muxer
|
||||
|
||||
proc closeMuxerHolder(muxerHolder: MuxerHolder) {.async.} =
|
||||
trace "Cleaning up muxer", m = muxerHolder.muxer
|
||||
|
||||
await muxerHolder.muxer.close()
|
||||
if not(isNil(muxerHolder.handle)):
|
||||
await muxer.close()
|
||||
if not(isNil(muxer.handler)):
|
||||
try:
|
||||
await muxerHolder.handle # TODO noraises?
|
||||
await muxer.handler # TODO noraises?
|
||||
except CatchableError as exc:
|
||||
trace "Exception in close muxer handler", exc = exc.msg
|
||||
trace "Cleaned up muxer", m = muxerHolder.muxer
|
||||
|
||||
proc delConn(c: ConnManager, conn: Connection) =
|
||||
let peerId = conn.peerId
|
||||
c.conns.withValue(peerId, peerConns):
|
||||
peerConns[].excl(conn)
|
||||
|
||||
if peerConns[].len == 0:
|
||||
c.conns.del(peerId) # invalidates `peerConns`
|
||||
|
||||
libp2p_peers.set(c.conns.len.int64)
|
||||
trace "Removed connection", conn
|
||||
|
||||
proc cleanupConn(c: ConnManager, conn: Connection) {.async.} =
|
||||
## clean connection's resources such as muxers and streams
|
||||
|
||||
if isNil(conn):
|
||||
trace "Wont cleanup a nil connection"
|
||||
return
|
||||
|
||||
# Remove connection from all tables without async breaks
|
||||
var muxer = some(MuxerHolder())
|
||||
if not c.muxed.pop(conn, muxer.get()):
|
||||
muxer = none(MuxerHolder)
|
||||
|
||||
delConn(c, conn)
|
||||
trace "Cleaned up muxer", m = muxer
|
||||
|
||||
proc muxCleanup(c: ConnManager, mux: Muxer) {.async.} =
|
||||
try:
|
||||
if muxer.isSome:
|
||||
await closeMuxerHolder(muxer.get())
|
||||
finally:
|
||||
await conn.close()
|
||||
trace "Triggering disconnect events", mux
|
||||
let peerId = mux.connection.peerId
|
||||
|
||||
trace "Connection cleaned up", conn
|
||||
let muxers = c.muxed.getOrDefault(peerId).filterIt(it != mux)
|
||||
if muxers.len > 0:
|
||||
c.muxed[peerId] = muxers
|
||||
else:
|
||||
c.muxed.del(peerId)
|
||||
libp2p_peers.set(c.muxed.len.int64)
|
||||
await c.triggerPeerEvents(peerId, PeerEvent(kind: PeerEventKind.Left))
|
||||
|
||||
proc onConnUpgraded(c: ConnManager, conn: Connection) {.async.} =
|
||||
try:
|
||||
trace "Triggering connect events", conn
|
||||
conn.upgrade()
|
||||
if not(c.peerStore.isNil):
|
||||
c.peerStore.cleanup(peerId)
|
||||
|
||||
let peerId = conn.peerId
|
||||
await c.triggerPeerEvents(
|
||||
peerId, PeerEvent(kind: PeerEventKind.Joined, initiator: conn.dir == Direction.Out))
|
||||
|
||||
await c.triggerConnEvent(
|
||||
peerId, ConnEvent(kind: ConnEventKind.Connected, incoming: conn.dir == Direction.In))
|
||||
except CatchableError as exc:
|
||||
# This is top-level procedure which will work as separate task, so it
|
||||
# do not need to propagate CancelledError and should handle other errors
|
||||
warn "Unexpected exception in switch peer connection cleanup",
|
||||
conn, msg = exc.msg
|
||||
|
||||
proc peerCleanup(c: ConnManager, conn: Connection) {.async.} =
|
||||
try:
|
||||
trace "Triggering disconnect events", conn
|
||||
let peerId = conn.peerId
|
||||
await c.triggerConnEvent(
|
||||
peerId, ConnEvent(kind: ConnEventKind.Disconnected))
|
||||
await c.triggerPeerEvents(peerId, PeerEvent(kind: PeerEventKind.Left))
|
||||
|
||||
if not(c.peerStore.isNil):
|
||||
c.peerStore.cleanup(peerId)
|
||||
except CatchableError as exc:
|
||||
# This is top-level procedure which will work as separate task, so it
|
||||
# do not need to propagate CancelledError and should handle other errors
|
||||
warn "Unexpected exception peer cleanup handler",
|
||||
conn, msg = exc.msg
|
||||
mux, msg = exc.msg
|
||||
|
||||
proc onClose(c: ConnManager, conn: Connection) {.async.} =
|
||||
proc onClose(c: ConnManager, mux: Muxer) {.async.} =
|
||||
## connection close even handler
|
||||
##
|
||||
## triggers the connections resource cleanup
|
||||
##
|
||||
try:
|
||||
await conn.join()
|
||||
trace "Connection closed, cleaning up", conn
|
||||
await c.cleanupConn(conn)
|
||||
except CancelledError:
|
||||
# This is top-level procedure which will work as separate task, so it
|
||||
# do not need to propagate CancelledError.
|
||||
debug "Unexpected cancellation in connection manager's cleanup", conn
|
||||
await mux.connection.join()
|
||||
trace "Connection closed, cleaning up", mux
|
||||
except CatchableError as exc:
|
||||
debug "Unexpected exception in connection manager's cleanup",
|
||||
errMsg = exc.msg, conn
|
||||
errMsg = exc.msg, mux
|
||||
finally:
|
||||
trace "Triggering peerCleanup", conn
|
||||
asyncSpawn c.peerCleanup(conn)
|
||||
await c.muxCleanup(mux)
|
||||
|
||||
proc selectConn*(c: ConnManager,
|
||||
proc selectMuxer*(c: ConnManager,
|
||||
peerId: PeerId,
|
||||
dir: Direction): Connection =
|
||||
dir: Direction): Muxer =
|
||||
## Select a connection for the provided peer and direction
|
||||
##
|
||||
let conns = toSeq(
|
||||
c.conns.getOrDefault(peerId))
|
||||
.filterIt( it.dir == dir )
|
||||
c.muxed.getOrDefault(peerId))
|
||||
.filterIt( it.connection.dir == dir )
|
||||
|
||||
if conns.len > 0:
|
||||
return conns[0]
|
||||
|
||||
proc selectConn*(c: ConnManager, peerId: PeerId): Connection =
|
||||
proc selectMuxer*(c: ConnManager, peerId: PeerId): Muxer =
|
||||
## Select a connection for the provided giving priority
|
||||
## to outgoing connections
|
||||
##
|
||||
|
||||
var conn = c.selectConn(peerId, Direction.Out)
|
||||
if isNil(conn):
|
||||
conn = c.selectConn(peerId, Direction.In)
|
||||
if isNil(conn):
|
||||
var mux = c.selectMuxer(peerId, Direction.Out)
|
||||
if isNil(mux):
|
||||
mux = c.selectMuxer(peerId, Direction.In)
|
||||
if isNil(mux):
|
||||
trace "connection not found", peerId
|
||||
return mux
|
||||
|
||||
return conn
|
||||
|
||||
proc selectMuxer*(c: ConnManager, conn: Connection): Muxer =
|
||||
## select the muxer for the provided connection
|
||||
proc storeMuxer*(c: ConnManager,
|
||||
muxer: Muxer)
|
||||
{.raises: [CatchableError].} =
|
||||
## store the connection and muxer
|
||||
##
|
||||
|
||||
if isNil(conn):
|
||||
return
|
||||
if isNil(muxer):
|
||||
raise newException(LPError, "muxer cannot be nil")
|
||||
|
||||
if conn in c.muxed:
|
||||
return c.muxed.getOrDefault(conn).muxer
|
||||
else:
|
||||
debug "no muxer for connection", conn
|
||||
if isNil(muxer.connection):
|
||||
raise newException(LPError, "muxer's connection cannot be nil")
|
||||
|
||||
proc storeConn*(c: ConnManager, conn: Connection)
|
||||
{.raises: [Defect, LPError].} =
|
||||
## store a connection
|
||||
##
|
||||
|
||||
if isNil(conn):
|
||||
raise newException(LPError, "Connection cannot be nil")
|
||||
|
||||
if conn.closed or conn.atEof:
|
||||
if muxer.connection.closed or muxer.connection.atEof:
|
||||
raise newException(LPError, "Connection closed or EOF")
|
||||
|
||||
let peerId = conn.peerId
|
||||
if c.conns.getOrDefault(peerId).len > c.maxConnsPerPeer:
|
||||
debug "Too many connections for peer",
|
||||
conn, conns = c.conns.getOrDefault(peerId).len
|
||||
let
|
||||
peerId = muxer.connection.peerId
|
||||
dir = muxer.connection.dir
|
||||
|
||||
raise newTooManyConnectionsError()
|
||||
# we use getOrDefault in the if below instead of [] to avoid the KeyError
|
||||
if c.muxed.getOrDefault(peerId).len > c.maxConnsPerPeer:
|
||||
let key = (peerId, dir)
|
||||
let expectedConn = c.expectedConnectionsOverLimit.getOrDefault(key)
|
||||
if expectedConn != nil and not expectedConn.finished:
|
||||
expectedConn.complete(muxer)
|
||||
else:
|
||||
debug "Too many connections for peer",
|
||||
conns = c.muxed.getOrDefault(peerId).len
|
||||
|
||||
c.conns.mgetOrPut(peerId, HashSet[Connection]()).incl(conn)
|
||||
libp2p_peers.set(c.conns.len.int64)
|
||||
raise newTooManyConnectionsError()
|
||||
|
||||
# Launch on close listener
|
||||
# All the errors are handled inside `onClose()` procedure.
|
||||
asyncSpawn c.onClose(conn)
|
||||
var newPeer = false
|
||||
c.muxed.withValue(peerId, muxers):
|
||||
doAssert muxers[].len > 0
|
||||
doAssert muxer notin muxers[]
|
||||
muxers[].add(muxer)
|
||||
do:
|
||||
c.muxed[peerId] = @[muxer]
|
||||
newPeer = true
|
||||
libp2p_peers.set(c.muxed.len.int64)
|
||||
|
||||
trace "Stored connection",
|
||||
conn, direction = $conn.dir, connections = c.conns.len
|
||||
asyncSpawn c.triggerConnEvent(
|
||||
peerId, ConnEvent(kind: ConnEventKind.Connected, incoming: dir == Direction.In))
|
||||
|
||||
proc trackConn(c: ConnManager,
|
||||
provider: ConnProvider,
|
||||
sema: AsyncSemaphore):
|
||||
Future[Connection] {.async.} =
|
||||
var conn: Connection
|
||||
try:
|
||||
conn = await provider()
|
||||
if newPeer:
|
||||
asyncSpawn c.triggerPeerEvents(
|
||||
peerId, PeerEvent(kind: PeerEventKind.Joined, initiator: dir == Direction.Out))
|
||||
|
||||
if isNil(conn):
|
||||
return
|
||||
asyncSpawn c.onClose(muxer)
|
||||
|
||||
trace "Got connection", conn
|
||||
trace "Stored muxer",
|
||||
muxer, direction = $muxer.connection.dir, peers = c.muxed.len
|
||||
|
||||
proc semaphoreMonitor() {.async.} =
|
||||
try:
|
||||
await conn.join()
|
||||
except CatchableError as exc:
|
||||
trace "Exception in semaphore monitor, ignoring", exc = exc.msg
|
||||
|
||||
sema.release()
|
||||
|
||||
asyncSpawn semaphoreMonitor()
|
||||
except CatchableError as exc:
|
||||
trace "Exception tracking connection", exc = exc.msg
|
||||
if not isNil(conn):
|
||||
await conn.close()
|
||||
|
||||
raise exc
|
||||
|
||||
return conn
|
||||
|
||||
proc trackIncomingConn*(c: ConnManager,
|
||||
provider: ConnProvider):
|
||||
Future[Connection] {.async.} =
|
||||
## await for a connection slot before attempting
|
||||
## to call the connection provider
|
||||
##
|
||||
|
||||
var conn: Connection
|
||||
try:
|
||||
trace "Tracking incoming connection"
|
||||
await c.inSema.acquire()
|
||||
conn = await c.trackConn(provider, c.inSema)
|
||||
if isNil(conn):
|
||||
trace "Couldn't acquire connection, releasing semaphore slot", dir = $Direction.In
|
||||
c.inSema.release()
|
||||
|
||||
return conn
|
||||
except CatchableError as exc:
|
||||
trace "Exception tracking connection", exc = exc.msg
|
||||
c.inSema.release()
|
||||
raise exc
|
||||
|
||||
proc trackOutgoingConn*(c: ConnManager,
|
||||
provider: ConnProvider,
|
||||
forceDial = false):
|
||||
Future[Connection] {.async.} =
|
||||
## try acquiring a connection if all slots
|
||||
## are already taken, raise TooManyConnectionsError
|
||||
## exception
|
||||
##
|
||||
|
||||
trace "Tracking outgoing connection", count = c.outSema.count,
|
||||
max = c.outSema.size
|
||||
proc getIncomingSlot*(c: ConnManager): Future[ConnectionSlot] {.async.} =
|
||||
await c.inSema.acquire()
|
||||
return ConnectionSlot(connManager: c, direction: In)
|
||||
|
||||
proc getOutgoingSlot*(c: ConnManager, forceDial = false): ConnectionSlot {.raises: [TooManyConnectionsError].} =
|
||||
if forceDial:
|
||||
c.outSema.forceAcquire()
|
||||
elif not c.outSema.tryAcquire():
|
||||
trace "Too many outgoing connections!", count = c.outSema.count,
|
||||
max = c.outSema.size
|
||||
raise newTooManyConnectionsError()
|
||||
return ConnectionSlot(connManager: c, direction: Out)
|
||||
|
||||
var conn: Connection
|
||||
try:
|
||||
conn = await c.trackConn(provider, c.outSema)
|
||||
if isNil(conn):
|
||||
trace "Couldn't acquire connection, releasing semaphore slot", dir = $Direction.Out
|
||||
c.outSema.release()
|
||||
proc slotsAvailable*(c: ConnManager, dir: Direction): int =
|
||||
case dir:
|
||||
of Direction.In:
|
||||
return c.inSema.count
|
||||
of Direction.Out:
|
||||
return c.outSema.count
|
||||
|
||||
return conn
|
||||
except CatchableError as exc:
|
||||
trace "Exception tracking connection", exc = exc.msg
|
||||
c.outSema.release()
|
||||
raise exc
|
||||
proc release*(cs: ConnectionSlot) =
|
||||
if cs.direction == In:
|
||||
cs.connManager.inSema.release()
|
||||
else:
|
||||
cs.connManager.outSema.release()
|
||||
|
||||
proc storeMuxer*(c: ConnManager,
|
||||
muxer: Muxer,
|
||||
handle: Future[void] = nil)
|
||||
{.raises: [Defect, CatchableError].} =
|
||||
## store the connection and muxer
|
||||
##
|
||||
proc trackConnection*(cs: ConnectionSlot, conn: Connection) =
|
||||
if isNil(conn):
|
||||
cs.release()
|
||||
return
|
||||
|
||||
if isNil(muxer):
|
||||
raise newException(CatchableError, "muxer cannot be nil")
|
||||
proc semaphoreMonitor() {.async.} =
|
||||
try:
|
||||
await conn.join()
|
||||
except CatchableError as exc:
|
||||
trace "Exception in semaphore monitor, ignoring", exc = exc.msg
|
||||
|
||||
if isNil(muxer.connection):
|
||||
raise newException(CatchableError, "muxer's connection cannot be nil")
|
||||
cs.release()
|
||||
|
||||
if muxer.connection notin c:
|
||||
raise newException(CatchableError, "cant add muxer for untracked connection")
|
||||
asyncSpawn semaphoreMonitor()
|
||||
|
||||
c.muxed[muxer.connection] = MuxerHolder(
|
||||
muxer: muxer,
|
||||
handle: handle)
|
||||
|
||||
trace "Stored muxer",
|
||||
muxer, handle = not handle.isNil, connections = c.conns.len
|
||||
|
||||
asyncSpawn c.onConnUpgraded(muxer.connection)
|
||||
proc trackMuxer*(cs: ConnectionSlot, mux: Muxer) =
|
||||
if isNil(mux):
|
||||
cs.release()
|
||||
return
|
||||
cs.trackConnection(mux.connection)
|
||||
|
||||
proc getStream*(c: ConnManager,
|
||||
peerId: PeerId,
|
||||
dir: Direction): Future[Connection] {.async, gcsafe.} =
|
||||
## get a muxed stream for the provided peer
|
||||
## with the given direction
|
||||
muxer: Muxer): Future[Connection] {.async.} =
|
||||
## get a muxed stream for the passed muxer
|
||||
##
|
||||
|
||||
let muxer = c.selectMuxer(c.selectConn(peerId, dir))
|
||||
if not(isNil(muxer)):
|
||||
return await muxer.newStream()
|
||||
|
||||
proc getStream*(c: ConnManager,
|
||||
peerId: PeerId): Future[Connection] {.async, gcsafe.} =
|
||||
peerId: PeerId): Future[Connection] {.async.} =
|
||||
## get a muxed stream for the passed peer from any connection
|
||||
##
|
||||
|
||||
let muxer = c.selectMuxer(c.selectConn(peerId))
|
||||
if not(isNil(muxer)):
|
||||
return await muxer.newStream()
|
||||
return await c.getStream(c.selectMuxer(peerId))
|
||||
|
||||
proc getStream*(c: ConnManager,
|
||||
conn: Connection): Future[Connection] {.async, gcsafe.} =
|
||||
## get a muxed stream for the passed connection
|
||||
peerId: PeerId,
|
||||
dir: Direction): Future[Connection] {.async.} =
|
||||
## get a muxed stream for the passed peer from a connection with `dir`
|
||||
##
|
||||
|
||||
let muxer = c.selectMuxer(conn)
|
||||
if not(isNil(muxer)):
|
||||
return await muxer.newStream()
|
||||
return await c.getStream(c.selectMuxer(peerId, dir))
|
||||
|
||||
|
||||
proc dropPeer*(c: ConnManager, peerId: PeerId) {.async.} =
|
||||
## drop connections and cleanup resources for peer
|
||||
##
|
||||
trace "Dropping peer", peerId
|
||||
let conns = c.conns.getOrDefault(peerId)
|
||||
for conn in conns:
|
||||
trace "Removing connection", conn
|
||||
delConn(c, conn)
|
||||
|
||||
var muxers: seq[MuxerHolder]
|
||||
for conn in conns:
|
||||
if conn in c.muxed:
|
||||
muxers.add c.muxed[conn]
|
||||
c.muxed.del(conn)
|
||||
let muxers = c.muxed.getOrDefault(peerId)
|
||||
|
||||
for muxer in muxers:
|
||||
await closeMuxerHolder(muxer)
|
||||
|
||||
for conn in conns:
|
||||
await conn.close()
|
||||
trace "Dropped peer", peerId
|
||||
await closeMuxer(muxer)
|
||||
|
||||
trace "Peer dropped", peerId
|
||||
|
||||
@@ -570,17 +421,18 @@ proc close*(c: ConnManager) {.async.} =
|
||||
##
|
||||
|
||||
trace "Closing ConnManager"
|
||||
let conns = c.conns
|
||||
c.conns.clear()
|
||||
|
||||
let muxed = c.muxed
|
||||
c.muxed.clear()
|
||||
|
||||
for _, muxer in muxed:
|
||||
await closeMuxerHolder(muxer)
|
||||
let expected = c.expectedConnectionsOverLimit
|
||||
c.expectedConnectionsOverLimit.clear()
|
||||
|
||||
for _, conns2 in conns:
|
||||
for conn in conns2:
|
||||
await conn.close()
|
||||
for _, fut in expected:
|
||||
await fut.cancelAndWait()
|
||||
|
||||
for _, muxers in muxed:
|
||||
for mux in muxers:
|
||||
await closeMuxer(mux)
|
||||
|
||||
trace "Closed ConnManager"
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2020-2022 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module integrates BearSSL ChaCha20+Poly1305
|
||||
##
|
||||
@@ -15,18 +15,11 @@
|
||||
|
||||
# RFC @ https://tools.ietf.org/html/rfc7539
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl
|
||||
import bearssl/blockx
|
||||
from stew/assign2 import assign
|
||||
from stew/ranges/ptr_arith import baseAddr
|
||||
|
||||
# have to do this due to a nim bug and raises[] on callbacks
|
||||
# https://github.com/nim-lang/Nim/issues/13905
|
||||
proc ourPoly1305CtmulRun*(key: pointer; iv: pointer; data: pointer; len: int;
|
||||
aad: pointer; aadLen: int; tag: pointer; ichacha: pointer;
|
||||
encrypt: cint) {.cdecl, importc: "br_poly1305_ctmul_run",
|
||||
header: "bearssl_block.h".}
|
||||
from stew/ptrops import baseAddr
|
||||
|
||||
const
|
||||
ChaChaPolyKeySize = 32
|
||||
@@ -67,15 +60,16 @@ proc encrypt*(_: type[ChaChaPoly],
|
||||
else:
|
||||
nil
|
||||
|
||||
ourPoly1305CtmulRun(
|
||||
poly1305CtmulRun(
|
||||
unsafeAddr key[0],
|
||||
unsafeAddr nonce[0],
|
||||
baseAddr(data),
|
||||
data.len,
|
||||
uint(data.len),
|
||||
ad,
|
||||
aad.len,
|
||||
uint(aad.len),
|
||||
baseAddr(tag),
|
||||
chacha20CtRun,
|
||||
# cast is required to workaround https://github.com/nim-lang/Nim/issues/13905
|
||||
cast[Chacha20Run](chacha20CtRun),
|
||||
#[encrypt]# 1.cint)
|
||||
|
||||
proc decrypt*(_: type[ChaChaPoly],
|
||||
@@ -90,13 +84,14 @@ proc decrypt*(_: type[ChaChaPoly],
|
||||
else:
|
||||
nil
|
||||
|
||||
ourPoly1305CtmulRun(
|
||||
poly1305CtmulRun(
|
||||
unsafeAddr key[0],
|
||||
unsafeAddr nonce[0],
|
||||
baseAddr(data),
|
||||
data.len,
|
||||
uint(data.len),
|
||||
ad,
|
||||
aad.len,
|
||||
uint(aad.len),
|
||||
baseAddr(tag),
|
||||
chacha20CtRun,
|
||||
# cast is required to workaround https://github.com/nim-lang/Nim/issues/13905
|
||||
cast[Chacha20Run](chacha20CtRun),
|
||||
#[decrypt]# 0.cint)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements Public Key and Private Key interface for libp2p.
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
from strutils import split, strip, cmpIgnoreCase
|
||||
|
||||
@@ -65,29 +65,29 @@ when supported(PKScheme.Ed25519):
|
||||
import ed25519/ed25519
|
||||
when supported(PKScheme.Secp256k1):
|
||||
import secp
|
||||
when supported(PKScheme.ECDSA):
|
||||
import ecnist
|
||||
|
||||
# We are still importing `ecnist` because, it is used for SECIO handshake,
|
||||
# but it will be impossible to create ECNIST keys or import ECNIST keys.
|
||||
# These used to be declared in `crypto` itself
|
||||
export ecnist.ephemeral, ecnist.ECDHEScheme
|
||||
|
||||
import ecnist, bearssl
|
||||
import bearssl/rand, bearssl/hash as bhash
|
||||
import ../protobuf/minprotobuf, ../vbuffer, ../multihash, ../multicodec
|
||||
import nimcrypto/[rijndael, twofish, sha2, hash, hmac]
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
import nimcrypto/utils as ncrutils
|
||||
import ../utility
|
||||
import stew/results
|
||||
export results
|
||||
export results, utility
|
||||
|
||||
# This is workaround for Nim's `import` bug
|
||||
export rijndael, twofish, sha2, hash, hmac, ncrutils
|
||||
export rijndael, twofish, sha2, hash, hmac, ncrutils, rand
|
||||
|
||||
type
|
||||
DigestSheme* = enum
|
||||
Sha256,
|
||||
Sha512
|
||||
|
||||
ECDHEScheme* = EcCurveKind
|
||||
|
||||
PublicKey* = object
|
||||
case scheme*: PKScheme
|
||||
of PKScheme.RSA:
|
||||
@@ -158,26 +158,28 @@ type
|
||||
template orError*(exp: untyped, err: untyped): untyped =
|
||||
(exp.mapErr do (_: auto) -> auto: err)
|
||||
|
||||
proc newRng*(): ref BrHmacDrbgContext =
|
||||
proc newRng*(): ref HmacDrbgContext =
|
||||
# You should only create one instance of the RNG per application / library
|
||||
# Ref is used so that it can be shared between components
|
||||
# TODO consider moving to bearssl
|
||||
var seeder = brPrngSeederSystem(nil)
|
||||
var seeder = prngSeederSystem(nil)
|
||||
if seeder == nil:
|
||||
return nil
|
||||
|
||||
var rng = (ref BrHmacDrbgContext)()
|
||||
brHmacDrbgInit(addr rng[], addr sha256Vtable, nil, 0)
|
||||
var rng = (ref HmacDrbgContext)()
|
||||
hmacDrbgInit(rng[], addr sha256Vtable, nil, 0)
|
||||
if seeder(addr rng.vtable) == 0:
|
||||
return nil
|
||||
rng
|
||||
|
||||
proc shuffle*[T](
|
||||
rng: ref BrHmacDrbgContext,
|
||||
rng: ref HmacDrbgContext,
|
||||
x: var openArray[T]) =
|
||||
|
||||
if x.len == 0: return
|
||||
|
||||
var randValues = newSeqUninitialized[byte](len(x) * 2)
|
||||
brHmacDrbgGenerate(rng[], randValues)
|
||||
hmacDrbgGenerate(rng[], randValues)
|
||||
|
||||
for i in countdown(x.high, 1):
|
||||
let
|
||||
@@ -186,7 +188,7 @@ proc shuffle*[T](
|
||||
swap(x[i], x[y])
|
||||
|
||||
proc random*(T: typedesc[PrivateKey], scheme: PKScheme,
|
||||
rng: var BrHmacDrbgContext,
|
||||
rng: var HmacDrbgContext,
|
||||
bits = RsaDefaultKeySize): CryptoResult[PrivateKey] =
|
||||
## Generate random private key for scheme ``scheme``.
|
||||
##
|
||||
@@ -218,7 +220,7 @@ proc random*(T: typedesc[PrivateKey], scheme: PKScheme,
|
||||
else:
|
||||
err(SchemeError)
|
||||
|
||||
proc random*(T: typedesc[PrivateKey], rng: var BrHmacDrbgContext,
|
||||
proc random*(T: typedesc[PrivateKey], rng: var HmacDrbgContext,
|
||||
bits = RsaDefaultKeySize): CryptoResult[PrivateKey] =
|
||||
## Generate random private key using default public-key cryptography scheme.
|
||||
##
|
||||
@@ -242,7 +244,7 @@ proc random*(T: typedesc[PrivateKey], rng: var BrHmacDrbgContext,
|
||||
err(SchemeError)
|
||||
|
||||
proc random*(T: typedesc[KeyPair], scheme: PKScheme,
|
||||
rng: var BrHmacDrbgContext,
|
||||
rng: var HmacDrbgContext,
|
||||
bits = RsaDefaultKeySize): CryptoResult[KeyPair] =
|
||||
## Generate random key pair for scheme ``scheme``.
|
||||
##
|
||||
@@ -282,7 +284,7 @@ proc random*(T: typedesc[KeyPair], scheme: PKScheme,
|
||||
else:
|
||||
err(SchemeError)
|
||||
|
||||
proc random*(T: typedesc[KeyPair], rng: var BrHmacDrbgContext,
|
||||
proc random*(T: typedesc[KeyPair], rng: var HmacDrbgContext,
|
||||
bits = RsaDefaultKeySize): CryptoResult[KeyPair] =
|
||||
## Generate random private pair of keys using default public-key cryptography
|
||||
## scheme.
|
||||
@@ -453,7 +455,8 @@ proc getBytes*(sig: Signature): seq[byte] =
|
||||
## Return signature ``sig`` in binary form.
|
||||
result = sig.data
|
||||
|
||||
proc init*[T: PrivateKey|PublicKey](key: var T, data: openArray[byte]): bool =
|
||||
template initImpl[T: PrivateKey|PublicKey](
|
||||
key: var T, data: openArray[byte]): bool =
|
||||
## Initialize private key ``key`` from libp2p's protobuf serialized raw
|
||||
## binary form.
|
||||
##
|
||||
@@ -466,7 +469,7 @@ proc init*[T: PrivateKey|PublicKey](key: var T, data: openArray[byte]): bool =
|
||||
var pb = initProtoBuffer(@data)
|
||||
let r1 = pb.getField(1, id)
|
||||
let r2 = pb.getField(2, buffer)
|
||||
if not(r1.isOk() and r1.get() and r2.isOk() and r2.get()):
|
||||
if not(r1.get(false) and r2.get(false)):
|
||||
false
|
||||
else:
|
||||
if cast[int8](id) notin SupportedSchemesInt or len(buffer) <= 0:
|
||||
@@ -515,6 +518,14 @@ proc init*[T: PrivateKey|PublicKey](key: var T, data: openArray[byte]): bool =
|
||||
else:
|
||||
false
|
||||
|
||||
{.push warning[ProveField]:off.} # https://github.com/nim-lang/Nim/issues/22060
|
||||
proc init*(key: var PrivateKey, data: openArray[byte]): bool =
|
||||
initImpl(key, data)
|
||||
|
||||
proc init*(key: var PublicKey, data: openArray[byte]): bool =
|
||||
initImpl(key, data)
|
||||
{.pop.}
|
||||
|
||||
proc init*(sig: var Signature, data: openArray[byte]): bool =
|
||||
## Initialize signature ``sig`` from raw binary form.
|
||||
##
|
||||
@@ -868,34 +879,6 @@ proc mac*(secret: Secret, id: int): seq[byte] {.inline.} =
|
||||
offset += secret.ivsize + secret.keysize
|
||||
copyMem(addr result[0], unsafeAddr secret.data[offset], secret.macsize)
|
||||
|
||||
proc ephemeral*(
|
||||
scheme: ECDHEScheme,
|
||||
rng: var BrHmacDrbgContext): CryptoResult[EcKeyPair] =
|
||||
## Generate ephemeral keys used to perform ECDHE.
|
||||
var keypair: EcKeyPair
|
||||
if scheme == Secp256r1:
|
||||
keypair = ? EcKeyPair.random(Secp256r1, rng).orError(KeyError)
|
||||
elif scheme == Secp384r1:
|
||||
keypair = ? EcKeyPair.random(Secp384r1, rng).orError(KeyError)
|
||||
elif scheme == Secp521r1:
|
||||
keypair = ? EcKeyPair.random(Secp521r1, rng).orError(KeyError)
|
||||
ok(keypair)
|
||||
|
||||
proc ephemeral*(
|
||||
scheme: string, rng: var BrHmacDrbgContext): CryptoResult[EcKeyPair] =
|
||||
## Generate ephemeral keys used to perform ECDHE using string encoding.
|
||||
##
|
||||
## Currently supported encoding strings are P-256, P-384, P-521, if encoding
|
||||
## string is not supported P-521 key will be generated.
|
||||
if scheme == "P-256":
|
||||
ephemeral(Secp256r1, rng)
|
||||
elif scheme == "P-384":
|
||||
ephemeral(Secp384r1, rng)
|
||||
elif scheme == "P-521":
|
||||
ephemeral(Secp521r1, rng)
|
||||
else:
|
||||
ephemeral(Secp521r1, rng)
|
||||
|
||||
proc getOrder*(remotePubkey, localNonce: openArray[byte],
|
||||
localPubkey, remoteNonce: openArray[byte]): CryptoResult[int] =
|
||||
## Compare values and calculate `order` parameter.
|
||||
@@ -941,84 +924,30 @@ proc selectBest*(order: int, p1, p2: string): string =
|
||||
if felement == selement:
|
||||
return felement
|
||||
|
||||
proc createProposal*(nonce, pubkey: openArray[byte],
|
||||
exchanges, ciphers, hashes: string): seq[byte] =
|
||||
## Create SecIO proposal message using random ``nonce``, local public key
|
||||
## ``pubkey``, comma-delimieted list of supported exchange schemes
|
||||
## ``exchanges``, comma-delimeted list of supported ciphers ``ciphers`` and
|
||||
## comma-delimeted list of supported hashes ``hashes``.
|
||||
var msg = initProtoBuffer({WithUint32BeLength})
|
||||
msg.write(1, nonce)
|
||||
msg.write(2, pubkey)
|
||||
msg.write(3, exchanges)
|
||||
msg.write(4, ciphers)
|
||||
msg.write(5, hashes)
|
||||
msg.finish()
|
||||
msg.buffer
|
||||
|
||||
proc decodeProposal*(message: seq[byte], nonce, pubkey: var seq[byte],
|
||||
exchanges, ciphers, hashes: var string): bool =
|
||||
## Parse incoming proposal message and decode remote random nonce ``nonce``,
|
||||
## remote public key ``pubkey``, comma-delimieted list of supported exchange
|
||||
## schemes ``exchanges``, comma-delimeted list of supported ciphers
|
||||
## ``ciphers`` and comma-delimeted list of supported hashes ``hashes``.
|
||||
##
|
||||
## Procedure returns ``true`` on success and ``false`` on error.
|
||||
var pb = initProtoBuffer(message)
|
||||
let r1 = pb.getField(1, nonce)
|
||||
let r2 = pb.getField(2, pubkey)
|
||||
let r3 = pb.getField(3, exchanges)
|
||||
let r4 = pb.getField(4, ciphers)
|
||||
let r5 = pb.getField(5, hashes)
|
||||
|
||||
r1.isOk() and r1.get() and r2.isOk() and r2.get() and
|
||||
r3.isOk() and r3.get() and r4.isOk() and r4.get() and
|
||||
r5.isOk() and r5.get()
|
||||
|
||||
proc createExchange*(epubkey, signature: openArray[byte]): seq[byte] =
|
||||
## Create SecIO exchange message using ephemeral public key ``epubkey`` and
|
||||
## signature of proposal blocks ``signature``.
|
||||
var msg = initProtoBuffer({WithUint32BeLength})
|
||||
msg.write(1, epubkey)
|
||||
msg.write(2, signature)
|
||||
msg.finish()
|
||||
msg.buffer
|
||||
|
||||
proc decodeExchange*(message: seq[byte],
|
||||
pubkey, signature: var seq[byte]): bool =
|
||||
## Parse incoming exchange message and decode remote ephemeral public key
|
||||
## ``pubkey`` and signature ``signature``.
|
||||
##
|
||||
## Procedure returns ``true`` on success and ``false`` on error.
|
||||
var pb = initProtoBuffer(message)
|
||||
let r1 = pb.getField(1, pubkey)
|
||||
let r2 = pb.getField(2, signature)
|
||||
r1.isOk() and r1.get() and r2.isOk() and r2.get()
|
||||
|
||||
## Serialization/Deserialization helpers
|
||||
|
||||
proc write*(vb: var VBuffer, pubkey: PublicKey) {.
|
||||
inline, raises: [Defect, ResultError[CryptoError]].} =
|
||||
inline, raises: [ResultError[CryptoError]].} =
|
||||
## Write PublicKey value ``pubkey`` to buffer ``vb``.
|
||||
vb.writeSeq(pubkey.getBytes().tryGet())
|
||||
|
||||
proc write*(vb: var VBuffer, seckey: PrivateKey) {.
|
||||
inline, raises: [Defect, ResultError[CryptoError]].} =
|
||||
inline, raises: [ResultError[CryptoError]].} =
|
||||
## Write PrivateKey value ``seckey`` to buffer ``vb``.
|
||||
vb.writeSeq(seckey.getBytes().tryGet())
|
||||
|
||||
proc write*(vb: var VBuffer, sig: PrivateKey) {.
|
||||
inline, raises: [Defect, ResultError[CryptoError]].} =
|
||||
inline, raises: [ResultError[CryptoError]].} =
|
||||
## Write Signature value ``sig`` to buffer ``vb``.
|
||||
vb.writeSeq(sig.getBytes().tryGet())
|
||||
|
||||
proc write*[T: PublicKey|PrivateKey](pb: var ProtoBuffer, field: int,
|
||||
key: T) {.
|
||||
inline, raises: [Defect, ResultError[CryptoError]].} =
|
||||
inline, raises: [ResultError[CryptoError]].} =
|
||||
write(pb, field, key.getBytes().tryGet())
|
||||
|
||||
proc write*(pb: var ProtoBuffer, field: int, sig: Signature) {.
|
||||
inline, raises: [Defect].} =
|
||||
inline, raises: [].} =
|
||||
write(pb, field, sig.getBytes())
|
||||
|
||||
proc getField*[T: PublicKey|PrivateKey](pb: ProtoBuffer, field: int,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2020-2022 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module integrates BearSSL Cyrve25519 mul and mulgen
|
||||
##
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
# RFC @ https://tools.ietf.org/html/rfc7748
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl
|
||||
import bearssl/[ec, rand]
|
||||
import stew/results
|
||||
from stew/assign2 import assign
|
||||
export results
|
||||
@@ -28,7 +28,6 @@ const
|
||||
type
|
||||
Curve25519* = object
|
||||
Curve25519Key* = array[Curve25519KeySize, byte]
|
||||
pcuchar = ptr char
|
||||
Curve25519Error* = enum
|
||||
Curver25519GenError
|
||||
|
||||
@@ -46,7 +45,7 @@ proc byteswap(buf: var Curve25519Key) {.inline.} =
|
||||
buf[31 - i] = x
|
||||
|
||||
proc mul*(_: type[Curve25519], point: var Curve25519Key, multiplier: Curve25519Key) =
|
||||
let defaultBrEc = brEcGetDefault()
|
||||
let defaultBrEc = ecGetDefault()
|
||||
|
||||
# multiplier needs to be big-endian
|
||||
var
|
||||
@@ -54,15 +53,15 @@ proc mul*(_: type[Curve25519], point: var Curve25519Key, multiplier: Curve25519K
|
||||
multiplierBs.byteswap()
|
||||
let
|
||||
res = defaultBrEc.mul(
|
||||
cast[pcuchar](addr point[0]),
|
||||
addr point[0],
|
||||
Curve25519KeySize,
|
||||
cast[pcuchar](addr multiplierBs[0]),
|
||||
addr multiplierBs[0],
|
||||
Curve25519KeySize,
|
||||
EC_curve25519)
|
||||
assert res == 1
|
||||
|
||||
proc mulgen(_: type[Curve25519], dst: var Curve25519Key, point: Curve25519Key) =
|
||||
let defaultBrEc = brEcGetDefault()
|
||||
let defaultBrEc = ecGetDefault()
|
||||
|
||||
var
|
||||
rpoint = point
|
||||
@@ -70,20 +69,20 @@ proc mulgen(_: type[Curve25519], dst: var Curve25519Key, point: Curve25519Key) =
|
||||
|
||||
let
|
||||
size = defaultBrEc.mulgen(
|
||||
cast[pcuchar](addr dst[0]),
|
||||
cast[pcuchar](addr rpoint[0]),
|
||||
addr dst[0],
|
||||
addr rpoint[0],
|
||||
Curve25519KeySize,
|
||||
EC_curve25519)
|
||||
|
||||
|
||||
assert size == Curve25519KeySize
|
||||
|
||||
proc public*(private: Curve25519Key): Curve25519Key =
|
||||
Curve25519.mulgen(result, private)
|
||||
|
||||
proc random*(_: type[Curve25519Key], rng: var BrHmacDrbgContext): Curve25519Key =
|
||||
proc random*(_: type[Curve25519Key], rng: var HmacDrbgContext): Curve25519Key =
|
||||
var res: Curve25519Key
|
||||
let defaultBrEc = brEcGetDefault()
|
||||
let len = brEcKeygen(
|
||||
let defaultBrEc = ecGetDefault()
|
||||
let len = ecKeygen(
|
||||
addr rng.vtable, defaultBrEc, nil, addr res[0], EC_curve25519)
|
||||
# Per bearssl documentation, the keygen only fails if the curve is
|
||||
# unrecognised -
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements constant-time ECDSA and ECDHE for NIST elliptic
|
||||
## curves secp256r1, secp384r1 and secp521r1.
|
||||
@@ -14,14 +14,17 @@
|
||||
## BearSSL library <https://bearssl.org/>
|
||||
## Copyright(C) 2018 Thomas Pornin <pornin@bolet.org>.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl
|
||||
import bearssl/[ec, rand, hash]
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
import nimcrypto/utils as ncrutils
|
||||
import minasn1
|
||||
export minasn1.Asn1Error
|
||||
import stew/[results, ctops]
|
||||
|
||||
import ../utility
|
||||
|
||||
export results
|
||||
|
||||
const
|
||||
@@ -40,12 +43,12 @@ const
|
||||
|
||||
type
|
||||
EcPrivateKey* = ref object
|
||||
buffer*: array[BR_EC_KBUF_PRIV_MAX_SIZE, byte]
|
||||
key*: BrEcPrivateKey
|
||||
buffer*: array[EC_KBUF_PRIV_MAX_SIZE, byte]
|
||||
key*: ec.EcPrivateKey
|
||||
|
||||
EcPublicKey* = ref object
|
||||
buffer*: array[BR_EC_KBUF_PUB_MAX_SIZE, byte]
|
||||
key*: BrEcPublicKey
|
||||
buffer*: array[EC_KBUF_PUB_MAX_SIZE, byte]
|
||||
key*: ec.EcPublicKey
|
||||
|
||||
EcKeyPair* = object
|
||||
seckey*: EcPrivateKey
|
||||
@@ -55,9 +58,9 @@ type
|
||||
buffer*: seq[byte]
|
||||
|
||||
EcCurveKind* = enum
|
||||
Secp256r1 = BR_EC_SECP256R1,
|
||||
Secp384r1 = BR_EC_SECP384R1,
|
||||
Secp521r1 = BR_EC_SECP521R1
|
||||
Secp256r1 = EC_secp256r1,
|
||||
Secp384r1 = EC_secp384r1,
|
||||
Secp521r1 = EC_secp521r1
|
||||
|
||||
EcPKI* = EcPrivateKey | EcPublicKey | EcSignature
|
||||
|
||||
@@ -71,7 +74,7 @@ type
|
||||
EcResult*[T] = Result[T, EcError]
|
||||
|
||||
const
|
||||
EcSupportedCurvesCint* = {cint(Secp256r1), cint(Secp384r1), cint(Secp521r1)}
|
||||
EcSupportedCurvesCint* = @[cint(Secp256r1), cint(Secp384r1), cint(Secp521r1)]
|
||||
|
||||
proc `-`(x: uint32): uint32 {.inline.} =
|
||||
result = (0xFFFF_FFFF'u32 - x) + 1'u32
|
||||
@@ -101,15 +104,15 @@ proc checkScalar(scalar: openArray[byte], curve: cint): uint32 =
|
||||
## - ``scalar`` is lower than the curve ``order``.
|
||||
##
|
||||
## Otherwise, return ``0``.
|
||||
var impl = brEcGetDefault()
|
||||
var orderlen = 0
|
||||
var order = cast[ptr UncheckedArray[byte]](impl.order(curve, addr orderlen))
|
||||
var impl = ecGetDefault()
|
||||
var orderlen: uint = 0
|
||||
var order = cast[ptr UncheckedArray[byte]](impl.order(curve, orderlen))
|
||||
|
||||
var z = 0'u32
|
||||
var c = 0'i32
|
||||
for u in scalar:
|
||||
z = z or u
|
||||
if len(scalar) == orderlen:
|
||||
if len(scalar) == int(orderlen):
|
||||
for i in 0..<len(scalar):
|
||||
c = c or (-(cast[int32](EQ0(c))) and CMP(scalar[i], order[i]))
|
||||
else:
|
||||
@@ -119,12 +122,12 @@ proc checkScalar(scalar: openArray[byte], curve: cint): uint32 =
|
||||
proc checkPublic(key: openArray[byte], curve: cint): uint32 =
|
||||
## Return ``1`` if public key ``key`` is on curve.
|
||||
var ckey = @key
|
||||
var x = [0x00'u8, 0x01'u8]
|
||||
var impl = brEcGetDefault()
|
||||
var orderlen = 0
|
||||
discard impl.order(curve, addr orderlen)
|
||||
result = impl.mul(cast[ptr char](unsafeAddr ckey[0]), len(ckey),
|
||||
cast[ptr char](addr x[0]), len(x), curve)
|
||||
var x = [byte 0x00, 0x01]
|
||||
var impl = ecGetDefault()
|
||||
var orderlen: uint = 0
|
||||
discard impl.order(curve, orderlen)
|
||||
result = impl.mul(unsafeAddr ckey[0], uint(len(ckey)),
|
||||
addr x[0], uint(len(x)), curve)
|
||||
|
||||
proc getOffset(pubkey: EcPublicKey): int {.inline.} =
|
||||
let o = cast[uint](pubkey.key.q) - cast[uint](unsafeAddr pubkey.buffer[0])
|
||||
@@ -174,7 +177,7 @@ proc copy*[T: EcPKI](dst: var T, src: T): bool =
|
||||
dst.buffer = src.buffer
|
||||
dst.key.curve = src.key.curve
|
||||
dst.key.xlen = length
|
||||
dst.key.x = cast[ptr char](addr dst.buffer[offset])
|
||||
dst.key.x = addr dst.buffer[offset]
|
||||
result = true
|
||||
elif T is EcPublicKey:
|
||||
let length = src.key.qlen
|
||||
@@ -184,7 +187,7 @@ proc copy*[T: EcPKI](dst: var T, src: T): bool =
|
||||
dst.buffer = src.buffer
|
||||
dst.key.curve = src.key.curve
|
||||
dst.key.qlen = length
|
||||
dst.key.q = cast[ptr char](addr dst.buffer[offset])
|
||||
dst.key.q = addr dst.buffer[offset]
|
||||
result = true
|
||||
else:
|
||||
let length = len(src.buffer)
|
||||
@@ -230,17 +233,17 @@ proc clear*[T: EcPKI|EcKeyPair](pki: var T) =
|
||||
|
||||
proc random*(
|
||||
T: typedesc[EcPrivateKey], kind: EcCurveKind,
|
||||
rng: var BrHmacDrbgContext): EcResult[EcPrivateKey] =
|
||||
rng: var HmacDrbgContext): EcResult[EcPrivateKey] =
|
||||
## Generate new random EC private key using BearSSL's HMAC-SHA256-DRBG
|
||||
## algorithm.
|
||||
##
|
||||
## ``kind`` elliptic curve kind of your choice (secp256r1, secp384r1 or
|
||||
## secp521r1).
|
||||
var ecimp = brEcGetDefault()
|
||||
var ecimp = ecGetDefault()
|
||||
var res = new EcPrivateKey
|
||||
if brEcKeygen(addr rng.vtable, ecimp,
|
||||
if ecKeygen(addr rng.vtable, ecimp,
|
||||
addr res.key, addr res.buffer[0],
|
||||
cast[cint](kind)) == 0:
|
||||
safeConvert[cint](kind)) == 0:
|
||||
err(EcKeyGenError)
|
||||
else:
|
||||
ok(res)
|
||||
@@ -250,12 +253,12 @@ proc getPublicKey*(seckey: EcPrivateKey): EcResult[EcPublicKey] =
|
||||
if isNil(seckey):
|
||||
return err(EcKeyIncorrectError)
|
||||
|
||||
var ecimp = brEcGetDefault()
|
||||
var ecimp = ecGetDefault()
|
||||
if seckey.key.curve in EcSupportedCurvesCint:
|
||||
var res = new EcPublicKey
|
||||
assert res.buffer.len > getPublicKeyLength(cast[EcCurveKind](seckey.key.curve))
|
||||
if brEcComputePublicKey(ecimp, addr res.key,
|
||||
addr res.buffer[0], unsafeAddr seckey.key) == 0:
|
||||
if ecComputePub(ecimp, addr res.key,
|
||||
addr res.buffer[0], unsafeAddr seckey.key) == 0:
|
||||
err(EcKeyIncorrectError)
|
||||
else:
|
||||
ok(res)
|
||||
@@ -264,7 +267,7 @@ proc getPublicKey*(seckey: EcPrivateKey): EcResult[EcPublicKey] =
|
||||
|
||||
proc random*(
|
||||
T: typedesc[EcKeyPair], kind: EcCurveKind,
|
||||
rng: var BrHmacDrbgContext): EcResult[T] =
|
||||
rng: var HmacDrbgContext): EcResult[T] =
|
||||
## Generate new random EC private and public keypair using BearSSL's
|
||||
## HMAC-SHA256-DRBG algorithm.
|
||||
##
|
||||
@@ -373,24 +376,24 @@ proc toBytes*(seckey: EcPrivateKey, data: var openArray[byte]): EcResult[int] =
|
||||
var p = Asn1Composite.init(Asn1Tag.Sequence)
|
||||
var c0 = Asn1Composite.init(0)
|
||||
var c1 = Asn1Composite.init(1)
|
||||
if seckey.key.curve == BR_EC_SECP256R1:
|
||||
if seckey.key.curve == EC_secp256r1:
|
||||
c0.write(Asn1Tag.Oid, Asn1OidSecp256r1)
|
||||
elif seckey.key.curve == BR_EC_SECP384R1:
|
||||
elif seckey.key.curve == EC_secp384r1:
|
||||
c0.write(Asn1Tag.Oid, Asn1OidSecp384r1)
|
||||
elif seckey.key.curve == BR_EC_SECP521R1:
|
||||
elif seckey.key.curve == EC_secp521r1:
|
||||
c0.write(Asn1Tag.Oid, Asn1OidSecp521r1)
|
||||
c0.finish()
|
||||
offset = pubkey.getOffset()
|
||||
if offset < 0:
|
||||
return err(EcKeyIncorrectError)
|
||||
length = pubkey.key.qlen
|
||||
length = int(pubkey.key.qlen)
|
||||
c1.write(Asn1Tag.BitString,
|
||||
pubkey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
c1.finish()
|
||||
offset = seckey.getOffset()
|
||||
if offset < 0:
|
||||
return err(EcKeyIncorrectError)
|
||||
length = seckey.key.xlen
|
||||
length = int(seckey.key.xlen)
|
||||
p.write(1'u64)
|
||||
p.write(Asn1Tag.OctetString,
|
||||
seckey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
@@ -421,18 +424,18 @@ proc toBytes*(pubkey: EcPublicKey, data: var openArray[byte]): EcResult[int] =
|
||||
var p = Asn1Composite.init(Asn1Tag.Sequence)
|
||||
var c = Asn1Composite.init(Asn1Tag.Sequence)
|
||||
c.write(Asn1Tag.Oid, Asn1OidEcPublicKey)
|
||||
if pubkey.key.curve == BR_EC_SECP256R1:
|
||||
if pubkey.key.curve == EC_secp256r1:
|
||||
c.write(Asn1Tag.Oid, Asn1OidSecp256r1)
|
||||
elif pubkey.key.curve == BR_EC_SECP384R1:
|
||||
elif pubkey.key.curve == EC_secp384r1:
|
||||
c.write(Asn1Tag.Oid, Asn1OidSecp384r1)
|
||||
elif pubkey.key.curve == BR_EC_SECP521R1:
|
||||
elif pubkey.key.curve == EC_secp521r1:
|
||||
c.write(Asn1Tag.Oid, Asn1OidSecp521r1)
|
||||
c.finish()
|
||||
p.write(c)
|
||||
let offset = getOffset(pubkey)
|
||||
if offset < 0:
|
||||
return err(EcKeyIncorrectError)
|
||||
let length = pubkey.key.qlen
|
||||
let length = int(pubkey.key.qlen)
|
||||
p.write(Asn1Tag.BitString,
|
||||
pubkey.buffer.toOpenArray(offset, offset + length - 1))
|
||||
p.finish()
|
||||
@@ -627,19 +630,19 @@ proc init*(key: var EcPrivateKey, data: openArray[byte]): Result[void, Asn1Error
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if oid == Asn1OidSecp256r1:
|
||||
curve = cast[cint](Secp256r1)
|
||||
curve = safeConvert[cint](Secp256r1)
|
||||
elif oid == Asn1OidSecp384r1:
|
||||
curve = cast[cint](Secp384r1)
|
||||
curve = safeConvert[cint](Secp384r1)
|
||||
elif oid == Asn1OidSecp521r1:
|
||||
curve = cast[cint](Secp521r1)
|
||||
curve = safeConvert[cint](Secp521r1)
|
||||
else:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if checkScalar(raw.toOpenArray(), curve) == 1'u32:
|
||||
key = new EcPrivateKey
|
||||
copyMem(addr key.buffer[0], addr raw.buffer[raw.offset], raw.length)
|
||||
key.key.x = cast[ptr char](addr key.buffer[0])
|
||||
key.key.xlen = raw.length
|
||||
key.key.x = addr key.buffer[0]
|
||||
key.key.xlen = uint(raw.length)
|
||||
key.key.curve = curve
|
||||
ok()
|
||||
else:
|
||||
@@ -681,11 +684,11 @@ proc init*(pubkey: var EcPublicKey, data: openArray[byte]): Result[void, Asn1Err
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if oid == Asn1OidSecp256r1:
|
||||
curve = cast[cint](Secp256r1)
|
||||
curve = safeConvert[cint](Secp256r1)
|
||||
elif oid == Asn1OidSecp384r1:
|
||||
curve = cast[cint](Secp384r1)
|
||||
curve = safeConvert[cint](Secp384r1)
|
||||
elif oid == Asn1OidSecp521r1:
|
||||
curve = cast[cint](Secp521r1)
|
||||
curve = safeConvert[cint](Secp521r1)
|
||||
else:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
@@ -697,8 +700,8 @@ proc init*(pubkey: var EcPublicKey, data: openArray[byte]): Result[void, Asn1Err
|
||||
if checkPublic(raw.toOpenArray(), curve) != 0:
|
||||
pubkey = new EcPublicKey
|
||||
copyMem(addr pubkey.buffer[0], addr raw.buffer[raw.offset], raw.length)
|
||||
pubkey.key.q = cast[ptr char](addr pubkey.buffer[0])
|
||||
pubkey.key.qlen = raw.length
|
||||
pubkey.key.q = addr pubkey.buffer[0]
|
||||
pubkey.key.qlen = uint(raw.length)
|
||||
pubkey.key.curve = curve
|
||||
ok()
|
||||
else:
|
||||
@@ -771,13 +774,13 @@ proc initRaw*(key: var EcPrivateKey, data: openArray[byte]): bool =
|
||||
## Procedure returns ``true`` on success, ``false`` otherwise.
|
||||
var curve: cint
|
||||
if len(data) == SecKey256Length:
|
||||
curve = cast[cint](Secp256r1)
|
||||
curve = safeConvert[cint](Secp256r1)
|
||||
result = true
|
||||
elif len(data) == SecKey384Length:
|
||||
curve = cast[cint](Secp384r1)
|
||||
curve = safeConvert[cint](Secp384r1)
|
||||
result = true
|
||||
elif len(data) == SecKey521Length:
|
||||
curve = cast[cint](Secp521r1)
|
||||
curve = safeConvert[cint](Secp521r1)
|
||||
result = true
|
||||
if result:
|
||||
result = false
|
||||
@@ -785,8 +788,8 @@ proc initRaw*(key: var EcPrivateKey, data: openArray[byte]): bool =
|
||||
let length = len(data)
|
||||
key = new EcPrivateKey
|
||||
copyMem(addr key.buffer[0], unsafeAddr data[0], length)
|
||||
key.key.x = cast[ptr char](addr key.buffer[0])
|
||||
key.key.xlen = length
|
||||
key.key.x = addr key.buffer[0]
|
||||
key.key.xlen = uint(length)
|
||||
key.key.curve = curve
|
||||
result = true
|
||||
|
||||
@@ -802,13 +805,13 @@ proc initRaw*(pubkey: var EcPublicKey, data: openArray[byte]): bool =
|
||||
if len(data) > 0:
|
||||
if data[0] == 0x04'u8:
|
||||
if len(data) == PubKey256Length:
|
||||
curve = cast[cint](Secp256r1)
|
||||
curve = safeConvert[cint](Secp256r1)
|
||||
result = true
|
||||
elif len(data) == PubKey384Length:
|
||||
curve = cast[cint](Secp384r1)
|
||||
curve = safeConvert[cint](Secp384r1)
|
||||
result = true
|
||||
elif len(data) == PubKey521Length:
|
||||
curve = cast[cint](Secp521r1)
|
||||
curve = safeConvert[cint](Secp521r1)
|
||||
result = true
|
||||
if result:
|
||||
result = false
|
||||
@@ -816,8 +819,8 @@ proc initRaw*(pubkey: var EcPublicKey, data: openArray[byte]): bool =
|
||||
let length = len(data)
|
||||
pubkey = new EcPublicKey
|
||||
copyMem(addr pubkey.buffer[0], unsafeAddr data[0], length)
|
||||
pubkey.key.q = cast[ptr char](addr pubkey.buffer[0])
|
||||
pubkey.key.qlen = length
|
||||
pubkey.key.q = addr pubkey.buffer[0]
|
||||
pubkey.key.qlen = uint(length)
|
||||
pubkey.key.curve = curve
|
||||
result = true
|
||||
|
||||
@@ -883,7 +886,7 @@ proc scalarMul*(pub: EcPublicKey, sec: EcPrivateKey): EcPublicKey =
|
||||
##
|
||||
## Returns point in curve as ``pub * sec`` or ``nil`` otherwise.
|
||||
doAssert((not isNil(pub)) and (not isNil(sec)))
|
||||
var impl = brEcGetDefault()
|
||||
var impl = ecGetDefault()
|
||||
if sec.key.curve in EcSupportedCurvesCint:
|
||||
if pub.key.curve == sec.key.curve:
|
||||
var key = new EcPublicKey
|
||||
@@ -891,9 +894,9 @@ proc scalarMul*(pub: EcPublicKey, sec: EcPrivateKey): EcPublicKey =
|
||||
let poffset = key.getOffset()
|
||||
let soffset = sec.getOffset()
|
||||
if poffset >= 0 and soffset >= 0:
|
||||
let res = impl.mul(cast[ptr char](addr key.buffer[poffset]),
|
||||
let res = impl.mul(addr key.buffer[poffset],
|
||||
key.key.qlen,
|
||||
cast[ptr char](unsafeAddr sec.buffer[soffset]),
|
||||
unsafeAddr sec.buffer[soffset],
|
||||
sec.key.xlen,
|
||||
key.key.curve)
|
||||
if res != 0:
|
||||
@@ -913,11 +916,11 @@ proc toSecret*(pubkey: EcPublicKey, seckey: EcPrivateKey,
|
||||
doAssert((not isNil(pubkey)) and (not isNil(seckey)))
|
||||
var mult = scalarMul(pubkey, seckey)
|
||||
if not isNil(mult):
|
||||
if seckey.key.curve == BR_EC_SECP256R1:
|
||||
if seckey.key.curve == EC_secp256r1:
|
||||
result = Secret256Length
|
||||
elif seckey.key.curve == BR_EC_SECP384R1:
|
||||
elif seckey.key.curve == EC_secp384r1:
|
||||
result = Secret384Length
|
||||
elif seckey.key.curve == BR_EC_SECP521R1:
|
||||
elif seckey.key.curve == EC_secp521r1:
|
||||
result = Secret521Length
|
||||
if len(data) >= result:
|
||||
var qplus1 = cast[pointer](cast[uint](mult.key.q) + 1'u)
|
||||
@@ -941,20 +944,20 @@ proc sign*[T: byte|char](seckey: EcPrivateKey,
|
||||
## Get ECDSA signature of data ``message`` using private key ``seckey``.
|
||||
if isNil(seckey):
|
||||
return err(EcKeyIncorrectError)
|
||||
var hc: BrHashCompatContext
|
||||
var hc: HashCompatContext
|
||||
var hash: array[32, byte]
|
||||
var impl = brEcGetDefault()
|
||||
var impl = ecGetDefault()
|
||||
if seckey.key.curve in EcSupportedCurvesCint:
|
||||
var sig = new EcSignature
|
||||
sig.buffer = newSeq[byte](256)
|
||||
var kv = addr sha256Vtable
|
||||
kv.init(addr hc.vtable)
|
||||
if len(message) > 0:
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], len(message))
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
|
||||
else:
|
||||
kv.update(addr hc.vtable, nil, 0)
|
||||
kv.output(addr hc.vtable, addr hash[0])
|
||||
let res = brEcdsaSignAsn1(impl, kv, addr hash[0], addr seckey.key,
|
||||
kv.out(addr hc.vtable, addr hash[0])
|
||||
let res = ecdsaI31SignAsn1(impl, kv, addr hash[0], addr seckey.key,
|
||||
addr sig.buffer[0])
|
||||
# Clear context with initial value
|
||||
kv.init(addr hc.vtable)
|
||||
@@ -974,20 +977,50 @@ proc verify*[T: byte|char](sig: EcSignature, message: openArray[T],
|
||||
## Return ``true`` if message verification succeeded, ``false`` if
|
||||
## verification failed.
|
||||
doAssert((not isNil(sig)) and (not isNil(pubkey)))
|
||||
var hc: BrHashCompatContext
|
||||
var hc: HashCompatContext
|
||||
var hash: array[32, byte]
|
||||
var impl = brEcGetDefault()
|
||||
var impl = ecGetDefault()
|
||||
if pubkey.key.curve in EcSupportedCurvesCint:
|
||||
var kv = addr sha256Vtable
|
||||
kv.init(addr hc.vtable)
|
||||
if len(message) > 0:
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], len(message))
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
|
||||
else:
|
||||
kv.update(addr hc.vtable, nil, 0)
|
||||
kv.output(addr hc.vtable, addr hash[0])
|
||||
let res = brEcdsaVerifyAsn1(impl, addr hash[0], len(hash),
|
||||
unsafeAddr pubkey.key,
|
||||
addr sig.buffer[0], len(sig.buffer))
|
||||
kv.out(addr hc.vtable, addr hash[0])
|
||||
let res = ecdsaI31VrfyAsn1(impl, addr hash[0], uint(len(hash)),
|
||||
unsafeAddr pubkey.key,
|
||||
addr sig.buffer[0], uint(len(sig.buffer)))
|
||||
# Clear context with initial value
|
||||
kv.init(addr hc.vtable)
|
||||
result = (res == 1)
|
||||
|
||||
type ECDHEScheme* = EcCurveKind
|
||||
|
||||
proc ephemeral*(
|
||||
scheme: ECDHEScheme,
|
||||
rng: var HmacDrbgContext): EcResult[EcKeyPair] =
|
||||
## Generate ephemeral keys used to perform ECDHE.
|
||||
var keypair: EcKeyPair
|
||||
if scheme == Secp256r1:
|
||||
keypair = ? EcKeyPair.random(Secp256r1, rng)
|
||||
elif scheme == Secp384r1:
|
||||
keypair = ? EcKeyPair.random(Secp384r1, rng)
|
||||
elif scheme == Secp521r1:
|
||||
keypair = ? EcKeyPair.random(Secp521r1, rng)
|
||||
ok(keypair)
|
||||
|
||||
proc ephemeral*(
|
||||
scheme: string, rng: var HmacDrbgContext): EcResult[EcKeyPair] =
|
||||
## Generate ephemeral keys used to perform ECDHE using string encoding.
|
||||
##
|
||||
## Currently supported encoding strings are P-256, P-384, P-521, if encoding
|
||||
## string is not supported P-521 key will be generated.
|
||||
if scheme == "P-256":
|
||||
ephemeral(Secp256r1, rng)
|
||||
elif scheme == "P-384":
|
||||
ephemeral(Secp384r1, rng)
|
||||
elif scheme == "P-521":
|
||||
ephemeral(Secp521r1, rng)
|
||||
else:
|
||||
ephemeral(Secp521r1, rng)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements ED25519.
|
||||
## This is pure nim implementation of ED25519 ref10.
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements ED25519.
|
||||
## This code is a port of the public domain, "ref10" implementation of ed25519
|
||||
## from SUPERCOP.
|
||||
|
||||
{.push raises: Defect.}
|
||||
{.push raises: [].}
|
||||
|
||||
import constants, bearssl
|
||||
import bearssl/rand
|
||||
import constants
|
||||
import nimcrypto/[hash, sha2]
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
import nimcrypto/utils as ncrutils
|
||||
import stew/[results, ctops]
|
||||
|
||||
import ../../utility
|
||||
|
||||
export results
|
||||
|
||||
# This workaround needed because of some bugs in Nim Static[T].
|
||||
export hash, sha2
|
||||
export hash, sha2, rand
|
||||
|
||||
const
|
||||
EdPrivateKeySize* = 64
|
||||
@@ -166,15 +170,15 @@ proc feCopy(h: var Fe, f: Fe) =
|
||||
h[9] = f9
|
||||
|
||||
proc load_3(inp: openArray[byte]): uint64 =
|
||||
result = cast[uint64](inp[0])
|
||||
result = result or (cast[uint64](inp[1]) shl 8)
|
||||
result = result or (cast[uint64](inp[2]) shl 16)
|
||||
result = safeConvert[uint64](inp[0])
|
||||
result = result or (safeConvert[uint64](inp[1]) shl 8)
|
||||
result = result or (safeConvert[uint64](inp[2]) shl 16)
|
||||
|
||||
proc load_4(inp: openArray[byte]): uint64 =
|
||||
result = cast[uint64](inp[0])
|
||||
result = result or (cast[uint64](inp[1]) shl 8)
|
||||
result = result or (cast[uint64](inp[2]) shl 16)
|
||||
result = result or (cast[uint64](inp[3]) shl 24)
|
||||
result = safeConvert[uint64](inp[0])
|
||||
result = result or (safeConvert[uint64](inp[1]) shl 8)
|
||||
result = result or (safeConvert[uint64](inp[2]) shl 16)
|
||||
result = result or (safeConvert[uint64](inp[3]) shl 24)
|
||||
|
||||
proc feFromBytes(h: var Fe, s: openArray[byte]) =
|
||||
var c0, c1, c2, c3, c4, c5, c6, c7, c8, c9: int64
|
||||
@@ -295,106 +299,106 @@ proc feMul(h: var Fe, f, g: Fe) =
|
||||
var f5_2 = 2 * f5
|
||||
var f7_2 = 2 * f7
|
||||
var f9_2 = 2 * f9
|
||||
var f0g0 = cast[int64](f0) * cast[int64](g0)
|
||||
var f0g1 = cast[int64](f0) * cast[int64](g1)
|
||||
var f0g2 = cast[int64](f0) * cast[int64](g2)
|
||||
var f0g3 = cast[int64](f0) * cast[int64](g3)
|
||||
var f0g4 = cast[int64](f0) * cast[int64](g4)
|
||||
var f0g5 = cast[int64](f0) * cast[int64](g5)
|
||||
var f0g6 = cast[int64](f0) * cast[int64](g6)
|
||||
var f0g7 = cast[int64](f0) * cast[int64](g7)
|
||||
var f0g8 = cast[int64](f0) * cast[int64](g8)
|
||||
var f0g9 = cast[int64](f0) * cast[int64](g9)
|
||||
var f1g0 = cast[int64](f1) * cast[int64](g0)
|
||||
var f1g1_2 = cast[int64](f1_2) * cast[int64](g1)
|
||||
var f1g2 = cast[int64](f1) * cast[int64](g2)
|
||||
var f1g3_2 = cast[int64](f1_2) * cast[int64](g3)
|
||||
var f1g4 = cast[int64](f1) * cast[int64](g4)
|
||||
var f1g5_2 = cast[int64](f1_2) * cast[int64](g5)
|
||||
var f1g6 = cast[int64](f1) * cast[int64](g6)
|
||||
var f1g7_2 = cast[int64](f1_2) * cast[int64](g7)
|
||||
var f1g8 = cast[int64](f1) * cast[int64](g8)
|
||||
var f1g9_38 = cast[int64](f1_2) * cast[int64](g9_19)
|
||||
var f2g0 = cast[int64](f2) * cast[int64](g0)
|
||||
var f2g1 = cast[int64](f2) * cast[int64](g1)
|
||||
var f2g2 = cast[int64](f2) * cast[int64](g2)
|
||||
var f2g3 = cast[int64](f2) * cast[int64](g3)
|
||||
var f2g4 = cast[int64](f2) * cast[int64](g4)
|
||||
var f2g5 = cast[int64](f2) * cast[int64](g5)
|
||||
var f2g6 = cast[int64](f2) * cast[int64](g6)
|
||||
var f2g7 = cast[int64](f2) * cast[int64](g7)
|
||||
var f2g8_19 = cast[int64](f2) * cast[int64](g8_19)
|
||||
var f2g9_19 = cast[int64](f2) * cast[int64](g9_19)
|
||||
var f3g0 = cast[int64](f3) * cast[int64](g0)
|
||||
var f3g1_2 = cast[int64](f3_2) * cast[int64](g1)
|
||||
var f3g2 = cast[int64](f3) * cast[int64](g2)
|
||||
var f3g3_2 = cast[int64](f3_2) * cast[int64](g3)
|
||||
var f3g4 = cast[int64](f3) * cast[int64](g4)
|
||||
var f3g5_2 = cast[int64](f3_2) * cast[int64](g5)
|
||||
var f3g6 = cast[int64](f3) * cast[int64](g6)
|
||||
var f3g7_38 = cast[int64](f3_2) * cast[int64](g7_19)
|
||||
var f3g8_19 = cast[int64](f3) * cast[int64](g8_19)
|
||||
var f3g9_38 = cast[int64](f3_2) * cast[int64](g9_19)
|
||||
var f4g0 = cast[int64](f4) * cast[int64](g0)
|
||||
var f4g1 = cast[int64](f4) * cast[int64](g1)
|
||||
var f4g2 = cast[int64](f4) * cast[int64](g2)
|
||||
var f4g3 = cast[int64](f4) * cast[int64](g3)
|
||||
var f4g4 = cast[int64](f4) * cast[int64](g4)
|
||||
var f4g5 = cast[int64](f4) * cast[int64](g5)
|
||||
var f4g6_19 = cast[int64](f4) * cast[int64](g6_19)
|
||||
var f4g7_19 = cast[int64](f4) * cast[int64](g7_19)
|
||||
var f4g8_19 = cast[int64](f4) * cast[int64](g8_19)
|
||||
var f4g9_19 = cast[int64](f4) * cast[int64](g9_19)
|
||||
var f5g0 = cast[int64](f5) * cast[int64](g0)
|
||||
var f5g1_2 = cast[int64](f5_2) * cast[int64](g1)
|
||||
var f5g2 = cast[int64](f5) * cast[int64](g2)
|
||||
var f5g3_2 = cast[int64](f5_2) * cast[int64](g3)
|
||||
var f5g4 = cast[int64](f5) * cast[int64](g4)
|
||||
var f5g5_38 = cast[int64](f5_2) * cast[int64](g5_19)
|
||||
var f5g6_19 = cast[int64](f5) * cast[int64](g6_19)
|
||||
var f5g7_38 = cast[int64](f5_2) * cast[int64](g7_19)
|
||||
var f5g8_19 = cast[int64](f5) * cast[int64](g8_19)
|
||||
var f5g9_38 = cast[int64](f5_2) * cast[int64](g9_19)
|
||||
var f6g0 = cast[int64](f6) * cast[int64](g0)
|
||||
var f6g1 = cast[int64](f6) * cast[int64](g1)
|
||||
var f6g2 = cast[int64](f6) * cast[int64](g2)
|
||||
var f6g3 = cast[int64](f6) * cast[int64](g3)
|
||||
var f6g4_19 = cast[int64](f6) * cast[int64](g4_19)
|
||||
var f6g5_19 = cast[int64](f6) * cast[int64](g5_19)
|
||||
var f6g6_19 = cast[int64](f6) * cast[int64](g6_19)
|
||||
var f6g7_19 = cast[int64](f6) * cast[int64](g7_19)
|
||||
var f6g8_19 = cast[int64](f6) * cast[int64](g8_19)
|
||||
var f6g9_19 = cast[int64](f6) * cast[int64](g9_19)
|
||||
var f7g0 = cast[int64](f7) * cast[int64](g0)
|
||||
var f7g1_2 = cast[int64](f7_2) * cast[int64](g1)
|
||||
var f7g2 = cast[int64](f7) * cast[int64](g2)
|
||||
var f7g3_38 = cast[int64](f7_2) * cast[int64](g3_19)
|
||||
var f7g4_19 = cast[int64](f7) * cast[int64](g4_19)
|
||||
var f7g5_38 = cast[int64](f7_2) * cast[int64](g5_19)
|
||||
var f7g6_19 = cast[int64](f7) * cast[int64](g6_19)
|
||||
var f7g7_38 = cast[int64](f7_2) * cast[int64](g7_19)
|
||||
var f7g8_19 = cast[int64](f7) * cast[int64](g8_19)
|
||||
var f7g9_38 = cast[int64](f7_2) * cast[int64](g9_19)
|
||||
var f8g0 = cast[int64](f8) * cast[int64](g0)
|
||||
var f8g1 = cast[int64](f8) * cast[int64](g1)
|
||||
var f8g2_19 = cast[int64](f8) * cast[int64](g2_19)
|
||||
var f8g3_19 = cast[int64](f8) * cast[int64](g3_19)
|
||||
var f8g4_19 = cast[int64](f8) * cast[int64](g4_19)
|
||||
var f8g5_19 = cast[int64](f8) * cast[int64](g5_19)
|
||||
var f8g6_19 = cast[int64](f8) * cast[int64](g6_19)
|
||||
var f8g7_19 = cast[int64](f8) * cast[int64](g7_19)
|
||||
var f8g8_19 = cast[int64](f8) * cast[int64](g8_19)
|
||||
var f8g9_19 = cast[int64](f8) * cast[int64](g9_19)
|
||||
var f9g0 = cast[int64](f9) * cast[int64](g0)
|
||||
var f9g1_38 = cast[int64](f9_2) * cast[int64](g1_19)
|
||||
var f9g2_19 = cast[int64](f9) * cast[int64](g2_19)
|
||||
var f9g3_38 = cast[int64](f9_2) * cast[int64](g3_19)
|
||||
var f9g4_19 = cast[int64](f9) * cast[int64](g4_19)
|
||||
var f9g5_38 = cast[int64](f9_2) * cast[int64](g5_19)
|
||||
var f9g6_19 = cast[int64](f9) * cast[int64](g6_19)
|
||||
var f9g7_38 = cast[int64](f9_2) * cast[int64](g7_19)
|
||||
var f9g8_19 = cast[int64](f9) * cast[int64](g8_19)
|
||||
var f9g9_38 = cast[int64](f9_2) * cast[int64](g9_19)
|
||||
var f0g0 = safeConvert[int64](f0) * safeConvert[int64](g0)
|
||||
var f0g1 = safeConvert[int64](f0) * safeConvert[int64](g1)
|
||||
var f0g2 = safeConvert[int64](f0) * safeConvert[int64](g2)
|
||||
var f0g3 = safeConvert[int64](f0) * safeConvert[int64](g3)
|
||||
var f0g4 = safeConvert[int64](f0) * safeConvert[int64](g4)
|
||||
var f0g5 = safeConvert[int64](f0) * safeConvert[int64](g5)
|
||||
var f0g6 = safeConvert[int64](f0) * safeConvert[int64](g6)
|
||||
var f0g7 = safeConvert[int64](f0) * safeConvert[int64](g7)
|
||||
var f0g8 = safeConvert[int64](f0) * safeConvert[int64](g8)
|
||||
var f0g9 = safeConvert[int64](f0) * safeConvert[int64](g9)
|
||||
var f1g0 = safeConvert[int64](f1) * safeConvert[int64](g0)
|
||||
var f1g1_2 = safeConvert[int64](f1_2) * safeConvert[int64](g1)
|
||||
var f1g2 = safeConvert[int64](f1) * safeConvert[int64](g2)
|
||||
var f1g3_2 = safeConvert[int64](f1_2) * safeConvert[int64](g3)
|
||||
var f1g4 = safeConvert[int64](f1) * safeConvert[int64](g4)
|
||||
var f1g5_2 = safeConvert[int64](f1_2) * safeConvert[int64](g5)
|
||||
var f1g6 = safeConvert[int64](f1) * safeConvert[int64](g6)
|
||||
var f1g7_2 = safeConvert[int64](f1_2) * safeConvert[int64](g7)
|
||||
var f1g8 = safeConvert[int64](f1) * safeConvert[int64](g8)
|
||||
var f1g9_38 = safeConvert[int64](f1_2) * safeConvert[int64](g9_19)
|
||||
var f2g0 = safeConvert[int64](f2) * safeConvert[int64](g0)
|
||||
var f2g1 = safeConvert[int64](f2) * safeConvert[int64](g1)
|
||||
var f2g2 = safeConvert[int64](f2) * safeConvert[int64](g2)
|
||||
var f2g3 = safeConvert[int64](f2) * safeConvert[int64](g3)
|
||||
var f2g4 = safeConvert[int64](f2) * safeConvert[int64](g4)
|
||||
var f2g5 = safeConvert[int64](f2) * safeConvert[int64](g5)
|
||||
var f2g6 = safeConvert[int64](f2) * safeConvert[int64](g6)
|
||||
var f2g7 = safeConvert[int64](f2) * safeConvert[int64](g7)
|
||||
var f2g8_19 = safeConvert[int64](f2) * safeConvert[int64](g8_19)
|
||||
var f2g9_19 = safeConvert[int64](f2) * safeConvert[int64](g9_19)
|
||||
var f3g0 = safeConvert[int64](f3) * safeConvert[int64](g0)
|
||||
var f3g1_2 = safeConvert[int64](f3_2) * safeConvert[int64](g1)
|
||||
var f3g2 = safeConvert[int64](f3) * safeConvert[int64](g2)
|
||||
var f3g3_2 = safeConvert[int64](f3_2) * safeConvert[int64](g3)
|
||||
var f3g4 = safeConvert[int64](f3) * safeConvert[int64](g4)
|
||||
var f3g5_2 = safeConvert[int64](f3_2) * safeConvert[int64](g5)
|
||||
var f3g6 = safeConvert[int64](f3) * safeConvert[int64](g6)
|
||||
var f3g7_38 = safeConvert[int64](f3_2) * safeConvert[int64](g7_19)
|
||||
var f3g8_19 = safeConvert[int64](f3) * safeConvert[int64](g8_19)
|
||||
var f3g9_38 = safeConvert[int64](f3_2) * safeConvert[int64](g9_19)
|
||||
var f4g0 = safeConvert[int64](f4) * safeConvert[int64](g0)
|
||||
var f4g1 = safeConvert[int64](f4) * safeConvert[int64](g1)
|
||||
var f4g2 = safeConvert[int64](f4) * safeConvert[int64](g2)
|
||||
var f4g3 = safeConvert[int64](f4) * safeConvert[int64](g3)
|
||||
var f4g4 = safeConvert[int64](f4) * safeConvert[int64](g4)
|
||||
var f4g5 = safeConvert[int64](f4) * safeConvert[int64](g5)
|
||||
var f4g6_19 = safeConvert[int64](f4) * safeConvert[int64](g6_19)
|
||||
var f4g7_19 = safeConvert[int64](f4) * safeConvert[int64](g7_19)
|
||||
var f4g8_19 = safeConvert[int64](f4) * safeConvert[int64](g8_19)
|
||||
var f4g9_19 = safeConvert[int64](f4) * safeConvert[int64](g9_19)
|
||||
var f5g0 = safeConvert[int64](f5) * safeConvert[int64](g0)
|
||||
var f5g1_2 = safeConvert[int64](f5_2) * safeConvert[int64](g1)
|
||||
var f5g2 = safeConvert[int64](f5) * safeConvert[int64](g2)
|
||||
var f5g3_2 = safeConvert[int64](f5_2) * safeConvert[int64](g3)
|
||||
var f5g4 = safeConvert[int64](f5) * safeConvert[int64](g4)
|
||||
var f5g5_38 = safeConvert[int64](f5_2) * safeConvert[int64](g5_19)
|
||||
var f5g6_19 = safeConvert[int64](f5) * safeConvert[int64](g6_19)
|
||||
var f5g7_38 = safeConvert[int64](f5_2) * safeConvert[int64](g7_19)
|
||||
var f5g8_19 = safeConvert[int64](f5) * safeConvert[int64](g8_19)
|
||||
var f5g9_38 = safeConvert[int64](f5_2) * safeConvert[int64](g9_19)
|
||||
var f6g0 = safeConvert[int64](f6) * safeConvert[int64](g0)
|
||||
var f6g1 = safeConvert[int64](f6) * safeConvert[int64](g1)
|
||||
var f6g2 = safeConvert[int64](f6) * safeConvert[int64](g2)
|
||||
var f6g3 = safeConvert[int64](f6) * safeConvert[int64](g3)
|
||||
var f6g4_19 = safeConvert[int64](f6) * safeConvert[int64](g4_19)
|
||||
var f6g5_19 = safeConvert[int64](f6) * safeConvert[int64](g5_19)
|
||||
var f6g6_19 = safeConvert[int64](f6) * safeConvert[int64](g6_19)
|
||||
var f6g7_19 = safeConvert[int64](f6) * safeConvert[int64](g7_19)
|
||||
var f6g8_19 = safeConvert[int64](f6) * safeConvert[int64](g8_19)
|
||||
var f6g9_19 = safeConvert[int64](f6) * safeConvert[int64](g9_19)
|
||||
var f7g0 = safeConvert[int64](f7) * safeConvert[int64](g0)
|
||||
var f7g1_2 = safeConvert[int64](f7_2) * safeConvert[int64](g1)
|
||||
var f7g2 = safeConvert[int64](f7) * safeConvert[int64](g2)
|
||||
var f7g3_38 = safeConvert[int64](f7_2) * safeConvert[int64](g3_19)
|
||||
var f7g4_19 = safeConvert[int64](f7) * safeConvert[int64](g4_19)
|
||||
var f7g5_38 = safeConvert[int64](f7_2) * safeConvert[int64](g5_19)
|
||||
var f7g6_19 = safeConvert[int64](f7) * safeConvert[int64](g6_19)
|
||||
var f7g7_38 = safeConvert[int64](f7_2) * safeConvert[int64](g7_19)
|
||||
var f7g8_19 = safeConvert[int64](f7) * safeConvert[int64](g8_19)
|
||||
var f7g9_38 = safeConvert[int64](f7_2) * safeConvert[int64](g9_19)
|
||||
var f8g0 = safeConvert[int64](f8) * safeConvert[int64](g0)
|
||||
var f8g1 = safeConvert[int64](f8) * safeConvert[int64](g1)
|
||||
var f8g2_19 = safeConvert[int64](f8) * safeConvert[int64](g2_19)
|
||||
var f8g3_19 = safeConvert[int64](f8) * safeConvert[int64](g3_19)
|
||||
var f8g4_19 = safeConvert[int64](f8) * safeConvert[int64](g4_19)
|
||||
var f8g5_19 = safeConvert[int64](f8) * safeConvert[int64](g5_19)
|
||||
var f8g6_19 = safeConvert[int64](f8) * safeConvert[int64](g6_19)
|
||||
var f8g7_19 = safeConvert[int64](f8) * safeConvert[int64](g7_19)
|
||||
var f8g8_19 = safeConvert[int64](f8) * safeConvert[int64](g8_19)
|
||||
var f8g9_19 = safeConvert[int64](f8) * safeConvert[int64](g9_19)
|
||||
var f9g0 = safeConvert[int64](f9) * safeConvert[int64](g0)
|
||||
var f9g1_38 = safeConvert[int64](f9_2) * safeConvert[int64](g1_19)
|
||||
var f9g2_19 = safeConvert[int64](f9) * safeConvert[int64](g2_19)
|
||||
var f9g3_38 = safeConvert[int64](f9_2) * safeConvert[int64](g3_19)
|
||||
var f9g4_19 = safeConvert[int64](f9) * safeConvert[int64](g4_19)
|
||||
var f9g5_38 = safeConvert[int64](f9_2) * safeConvert[int64](g5_19)
|
||||
var f9g6_19 = safeConvert[int64](f9) * safeConvert[int64](g6_19)
|
||||
var f9g7_38 = safeConvert[int64](f9_2) * safeConvert[int64](g7_19)
|
||||
var f9g8_19 = safeConvert[int64](f9) * safeConvert[int64](g8_19)
|
||||
var f9g9_38 = safeConvert[int64](f9_2) * safeConvert[int64](g9_19)
|
||||
var
|
||||
c0, c1, c2, c3, c4, c5, c6, c7, c8, c9: int64
|
||||
h0: int64 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 +
|
||||
@@ -489,7 +493,7 @@ proc verify32(x: openArray[byte], y: openArray[byte]): int32 =
|
||||
proc feIsNegative(f: Fe): int32 =
|
||||
var s: array[32, byte]
|
||||
feToBytes(s, f)
|
||||
result = cast[int32](s[0] and 1'u8)
|
||||
result = safeConvert[int32](s[0] and 1'u8)
|
||||
|
||||
proc feIsNonZero(f: Fe): int32 =
|
||||
var s: array[32, byte]
|
||||
@@ -512,61 +516,61 @@ proc feSq(h: var Fe, f: Fe) =
|
||||
var f7_38: int32 = 38 * f7
|
||||
var f8_19: int32 = 19 * f8
|
||||
var f9_38: int32 = 38 * f9
|
||||
var f0f0: int64 = f0 * cast[int64](f0)
|
||||
var f0f1_2: int64 = f0_2 * cast[int64](f1)
|
||||
var f0f2_2: int64 = f0_2 * cast[int64](f2)
|
||||
var f0f3_2: int64 = f0_2 * cast[int64](f3)
|
||||
var f0f4_2: int64 = f0_2 * cast[int64](f4)
|
||||
var f0f5_2: int64 = f0_2 * cast[int64](f5)
|
||||
var f0f6_2: int64 = f0_2 * cast[int64](f6)
|
||||
var f0f7_2: int64 = f0_2 * cast[int64](f7)
|
||||
var f0f8_2: int64 = f0_2 * cast[int64](f8)
|
||||
var f0f9_2: int64 = f0_2 * cast[int64](f9)
|
||||
var f1f1_2: int64 = f1_2 * cast[int64](f1)
|
||||
var f1f2_2: int64 = f1_2 * cast[int64](f2)
|
||||
var f1f3_4: int64 = f1_2 * cast[int64](f3_2)
|
||||
var f1f4_2: int64 = f1_2 * cast[int64](f4)
|
||||
var f1f5_4: int64 = f1_2 * cast[int64](f5_2)
|
||||
var f1f6_2: int64 = f1_2 * cast[int64](f6)
|
||||
var f1f7_4: int64 = f1_2 * cast[int64](f7_2)
|
||||
var f1f8_2: int64 = f1_2 * cast[int64](f8)
|
||||
var f1f9_76: int64 = f1_2 * cast[int64](f9_38)
|
||||
var f2f2: int64 = f2 * cast[int64](f2)
|
||||
var f2f3_2: int64 = f2_2 * cast[int64](f3)
|
||||
var f2f4_2: int64 = f2_2 * cast[int64](f4)
|
||||
var f2f5_2: int64 = f2_2 * cast[int64](f5)
|
||||
var f2f6_2: int64 = f2_2 * cast[int64](f6)
|
||||
var f2f7_2: int64 = f2_2 * cast[int64](f7)
|
||||
var f2f8_38: int64 = f2_2 * cast[int64](f8_19)
|
||||
var f2f9_38: int64 = f2 * cast[int64](f9_38)
|
||||
var f3f3_2: int64 = f3_2 * cast[int64](f3)
|
||||
var f3f4_2: int64 = f3_2 * cast[int64](f4)
|
||||
var f3f5_4: int64 = f3_2 * cast[int64](f5_2)
|
||||
var f3f6_2: int64 = f3_2 * cast[int64](f6)
|
||||
var f3f7_76: int64 = f3_2 * cast[int64](f7_38)
|
||||
var f3f8_38: int64 = f3_2 * cast[int64](f8_19)
|
||||
var f3f9_76: int64 = f3_2 * cast[int64](f9_38)
|
||||
var f4f4: int64 = f4 * cast[int64](f4)
|
||||
var f4f5_2: int64 = f4_2 * cast[int64](f5)
|
||||
var f4f6_38: int64 = f4_2 * cast[int64](f6_19)
|
||||
var f4f7_38: int64 = f4 * cast[int64](f7_38)
|
||||
var f4f8_38: int64 = f4_2 * cast[int64](f8_19)
|
||||
var f4f9_38: int64 = f4 * cast[int64](f9_38)
|
||||
var f5f5_38: int64 = f5 * cast[int64](f5_38)
|
||||
var f5f6_38: int64 = f5_2 * cast[int64](f6_19)
|
||||
var f5f7_76: int64 = f5_2 * cast[int64](f7_38)
|
||||
var f5f8_38: int64 = f5_2 * cast[int64](f8_19)
|
||||
var f5f9_76: int64 = f5_2 * cast[int64](f9_38)
|
||||
var f6f6_19: int64 = f6 * cast[int64](f6_19)
|
||||
var f6f7_38: int64 = f6 * cast[int64](f7_38)
|
||||
var f6f8_38: int64 = f6_2 * cast[int64](f8_19)
|
||||
var f6f9_38: int64 = f6 * cast[int64](f9_38)
|
||||
var f7f7_38: int64 = f7 * cast[int64](f7_38)
|
||||
var f7f8_38: int64 = f7_2 * cast[int64](f8_19)
|
||||
var f7f9_76: int64 = f7_2 * cast[int64](f9_38)
|
||||
var f8f8_19: int64 = f8 * cast[int64](f8_19)
|
||||
var f8f9_38: int64 = f8 * cast[int64](f9_38)
|
||||
var f9f9_38: int64 = f9 * cast[int64](f9_38)
|
||||
var f0f0: int64 = f0 * safeConvert[int64](f0)
|
||||
var f0f1_2: int64 = f0_2 * safeConvert[int64](f1)
|
||||
var f0f2_2: int64 = f0_2 * safeConvert[int64](f2)
|
||||
var f0f3_2: int64 = f0_2 * safeConvert[int64](f3)
|
||||
var f0f4_2: int64 = f0_2 * safeConvert[int64](f4)
|
||||
var f0f5_2: int64 = f0_2 * safeConvert[int64](f5)
|
||||
var f0f6_2: int64 = f0_2 * safeConvert[int64](f6)
|
||||
var f0f7_2: int64 = f0_2 * safeConvert[int64](f7)
|
||||
var f0f8_2: int64 = f0_2 * safeConvert[int64](f8)
|
||||
var f0f9_2: int64 = f0_2 * safeConvert[int64](f9)
|
||||
var f1f1_2: int64 = f1_2 * safeConvert[int64](f1)
|
||||
var f1f2_2: int64 = f1_2 * safeConvert[int64](f2)
|
||||
var f1f3_4: int64 = f1_2 * safeConvert[int64](f3_2)
|
||||
var f1f4_2: int64 = f1_2 * safeConvert[int64](f4)
|
||||
var f1f5_4: int64 = f1_2 * safeConvert[int64](f5_2)
|
||||
var f1f6_2: int64 = f1_2 * safeConvert[int64](f6)
|
||||
var f1f7_4: int64 = f1_2 * safeConvert[int64](f7_2)
|
||||
var f1f8_2: int64 = f1_2 * safeConvert[int64](f8)
|
||||
var f1f9_76: int64 = f1_2 * safeConvert[int64](f9_38)
|
||||
var f2f2: int64 = f2 * safeConvert[int64](f2)
|
||||
var f2f3_2: int64 = f2_2 * safeConvert[int64](f3)
|
||||
var f2f4_2: int64 = f2_2 * safeConvert[int64](f4)
|
||||
var f2f5_2: int64 = f2_2 * safeConvert[int64](f5)
|
||||
var f2f6_2: int64 = f2_2 * safeConvert[int64](f6)
|
||||
var f2f7_2: int64 = f2_2 * safeConvert[int64](f7)
|
||||
var f2f8_38: int64 = f2_2 * safeConvert[int64](f8_19)
|
||||
var f2f9_38: int64 = f2 * safeConvert[int64](f9_38)
|
||||
var f3f3_2: int64 = f3_2 * safeConvert[int64](f3)
|
||||
var f3f4_2: int64 = f3_2 * safeConvert[int64](f4)
|
||||
var f3f5_4: int64 = f3_2 * safeConvert[int64](f5_2)
|
||||
var f3f6_2: int64 = f3_2 * safeConvert[int64](f6)
|
||||
var f3f7_76: int64 = f3_2 * safeConvert[int64](f7_38)
|
||||
var f3f8_38: int64 = f3_2 * safeConvert[int64](f8_19)
|
||||
var f3f9_76: int64 = f3_2 * safeConvert[int64](f9_38)
|
||||
var f4f4: int64 = f4 * safeConvert[int64](f4)
|
||||
var f4f5_2: int64 = f4_2 * safeConvert[int64](f5)
|
||||
var f4f6_38: int64 = f4_2 * safeConvert[int64](f6_19)
|
||||
var f4f7_38: int64 = f4 * safeConvert[int64](f7_38)
|
||||
var f4f8_38: int64 = f4_2 * safeConvert[int64](f8_19)
|
||||
var f4f9_38: int64 = f4 * safeConvert[int64](f9_38)
|
||||
var f5f5_38: int64 = f5 * safeConvert[int64](f5_38)
|
||||
var f5f6_38: int64 = f5_2 * safeConvert[int64](f6_19)
|
||||
var f5f7_76: int64 = f5_2 * safeConvert[int64](f7_38)
|
||||
var f5f8_38: int64 = f5_2 * safeConvert[int64](f8_19)
|
||||
var f5f9_76: int64 = f5_2 * safeConvert[int64](f9_38)
|
||||
var f6f6_19: int64 = f6 * safeConvert[int64](f6_19)
|
||||
var f6f7_38: int64 = f6 * safeConvert[int64](f7_38)
|
||||
var f6f8_38: int64 = f6_2 * safeConvert[int64](f8_19)
|
||||
var f6f9_38: int64 = f6 * safeConvert[int64](f9_38)
|
||||
var f7f7_38: int64 = f7 * safeConvert[int64](f7_38)
|
||||
var f7f8_38: int64 = f7_2 * safeConvert[int64](f8_19)
|
||||
var f7f9_76: int64 = f7_2 * safeConvert[int64](f9_38)
|
||||
var f8f8_19: int64 = f8 * safeConvert[int64](f8_19)
|
||||
var f8f9_38: int64 = f8 * safeConvert[int64](f9_38)
|
||||
var f9f9_38: int64 = f9 * safeConvert[int64](f9_38)
|
||||
var h0: int64 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38
|
||||
var h1: int64 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38
|
||||
var h2: int64 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19
|
||||
@@ -619,61 +623,61 @@ proc feSq2(h: var Fe, f: Fe) =
|
||||
var f7_38 = 38 * f7
|
||||
var f8_19 = 19 * f8
|
||||
var f9_38 = 38 * f9
|
||||
var f0f0 = cast[int64](f0) * cast[int64](f0)
|
||||
var f0f1_2 = cast[int64](f0_2) * cast[int64](f1)
|
||||
var f0f2_2 = cast[int64](f0_2) * cast[int64](f2)
|
||||
var f0f3_2 = cast[int64](f0_2) * cast[int64](f3)
|
||||
var f0f4_2 = cast[int64](f0_2) * cast[int64](f4)
|
||||
var f0f5_2 = cast[int64](f0_2) * cast[int64](f5)
|
||||
var f0f6_2 = cast[int64](f0_2) * cast[int64](f6)
|
||||
var f0f7_2 = cast[int64](f0_2) * cast[int64](f7)
|
||||
var f0f8_2 = cast[int64](f0_2) * cast[int64](f8)
|
||||
var f0f9_2 = cast[int64](f0_2) * cast[int64](f9)
|
||||
var f1f1_2 = cast[int64](f1_2) * cast[int64](f1)
|
||||
var f1f2_2 = cast[int64](f1_2) * cast[int64](f2)
|
||||
var f1f3_4 = cast[int64](f1_2) * cast[int64](f3_2)
|
||||
var f1f4_2 = cast[int64](f1_2) * cast[int64](f4)
|
||||
var f1f5_4 = cast[int64](f1_2) * cast[int64](f5_2)
|
||||
var f1f6_2 = cast[int64](f1_2) * cast[int64](f6)
|
||||
var f1f7_4 = cast[int64](f1_2) * cast[int64](f7_2)
|
||||
var f1f8_2 = cast[int64](f1_2) * cast[int64](f8)
|
||||
var f1f9_76 = cast[int64](f1_2) * cast[int64](f9_38)
|
||||
var f2f2 = cast[int64](f2) * cast[int64](f2)
|
||||
var f2f3_2 = cast[int64](f2_2) * cast[int64](f3)
|
||||
var f2f4_2 = cast[int64](f2_2) * cast[int64](f4)
|
||||
var f2f5_2 = cast[int64](f2_2) * cast[int64](f5)
|
||||
var f2f6_2 = cast[int64](f2_2) * cast[int64](f6)
|
||||
var f2f7_2 = cast[int64](f2_2) * cast[int64](f7)
|
||||
var f2f8_38 = cast[int64](f2_2) * cast[int64](f8_19)
|
||||
var f2f9_38 = cast[int64](f2) * cast[int64](f9_38)
|
||||
var f3f3_2 = cast[int64](f3_2) * cast[int64](f3)
|
||||
var f3f4_2 = cast[int64](f3_2) * cast[int64](f4)
|
||||
var f3f5_4 = cast[int64](f3_2) * cast[int64](f5_2)
|
||||
var f3f6_2 = cast[int64](f3_2) * cast[int64](f6)
|
||||
var f3f7_76 = cast[int64](f3_2) * cast[int64](f7_38)
|
||||
var f3f8_38 = cast[int64](f3_2) * cast[int64](f8_19)
|
||||
var f3f9_76 = cast[int64](f3_2) * cast[int64](f9_38)
|
||||
var f4f4 = cast[int64](f4) * cast[int64](f4)
|
||||
var f4f5_2 = cast[int64](f4_2) * cast[int64](f5)
|
||||
var f4f6_38 = cast[int64](f4_2) * cast[int64](f6_19)
|
||||
var f4f7_38 = cast[int64](f4) * cast[int64](f7_38)
|
||||
var f4f8_38 = cast[int64](f4_2) * cast[int64](f8_19)
|
||||
var f4f9_38 = cast[int64](f4) * cast[int64](f9_38)
|
||||
var f5f5_38 = cast[int64](f5) * cast[int64](f5_38)
|
||||
var f5f6_38 = cast[int64](f5_2) * cast[int64](f6_19)
|
||||
var f5f7_76 = cast[int64](f5_2) * cast[int64](f7_38)
|
||||
var f5f8_38 = cast[int64](f5_2) * cast[int64](f8_19)
|
||||
var f5f9_76 = cast[int64](f5_2) * cast[int64](f9_38)
|
||||
var f6f6_19 = cast[int64](f6) * cast[int64](f6_19)
|
||||
var f6f7_38 = cast[int64](f6) * cast[int64](f7_38)
|
||||
var f6f8_38 = cast[int64](f6_2) * cast[int64](f8_19)
|
||||
var f6f9_38 = cast[int64](f6) * cast[int64](f9_38)
|
||||
var f7f7_38 = cast[int64](f7) * cast[int64](f7_38)
|
||||
var f7f8_38 = cast[int64](f7_2) * cast[int64](f8_19)
|
||||
var f7f9_76 = cast[int64](f7_2) * cast[int64](f9_38)
|
||||
var f8f8_19 = cast[int64](f8) * cast[int64](f8_19)
|
||||
var f8f9_38 = cast[int64](f8) * cast[int64](f9_38)
|
||||
var f9f9_38 = cast[int64](f9) * cast[int64](f9_38)
|
||||
var f0f0 = safeConvert[int64](f0) * safeConvert[int64](f0)
|
||||
var f0f1_2 = safeConvert[int64](f0_2) * safeConvert[int64](f1)
|
||||
var f0f2_2 = safeConvert[int64](f0_2) * safeConvert[int64](f2)
|
||||
var f0f3_2 = safeConvert[int64](f0_2) * safeConvert[int64](f3)
|
||||
var f0f4_2 = safeConvert[int64](f0_2) * safeConvert[int64](f4)
|
||||
var f0f5_2 = safeConvert[int64](f0_2) * safeConvert[int64](f5)
|
||||
var f0f6_2 = safeConvert[int64](f0_2) * safeConvert[int64](f6)
|
||||
var f0f7_2 = safeConvert[int64](f0_2) * safeConvert[int64](f7)
|
||||
var f0f8_2 = safeConvert[int64](f0_2) * safeConvert[int64](f8)
|
||||
var f0f9_2 = safeConvert[int64](f0_2) * safeConvert[int64](f9)
|
||||
var f1f1_2 = safeConvert[int64](f1_2) * safeConvert[int64](f1)
|
||||
var f1f2_2 = safeConvert[int64](f1_2) * safeConvert[int64](f2)
|
||||
var f1f3_4 = safeConvert[int64](f1_2) * safeConvert[int64](f3_2)
|
||||
var f1f4_2 = safeConvert[int64](f1_2) * safeConvert[int64](f4)
|
||||
var f1f5_4 = safeConvert[int64](f1_2) * safeConvert[int64](f5_2)
|
||||
var f1f6_2 = safeConvert[int64](f1_2) * safeConvert[int64](f6)
|
||||
var f1f7_4 = safeConvert[int64](f1_2) * safeConvert[int64](f7_2)
|
||||
var f1f8_2 = safeConvert[int64](f1_2) * safeConvert[int64](f8)
|
||||
var f1f9_76 = safeConvert[int64](f1_2) * safeConvert[int64](f9_38)
|
||||
var f2f2 = safeConvert[int64](f2) * safeConvert[int64](f2)
|
||||
var f2f3_2 = safeConvert[int64](f2_2) * safeConvert[int64](f3)
|
||||
var f2f4_2 = safeConvert[int64](f2_2) * safeConvert[int64](f4)
|
||||
var f2f5_2 = safeConvert[int64](f2_2) * safeConvert[int64](f5)
|
||||
var f2f6_2 = safeConvert[int64](f2_2) * safeConvert[int64](f6)
|
||||
var f2f7_2 = safeConvert[int64](f2_2) * safeConvert[int64](f7)
|
||||
var f2f8_38 = safeConvert[int64](f2_2) * safeConvert[int64](f8_19)
|
||||
var f2f9_38 = safeConvert[int64](f2) * safeConvert[int64](f9_38)
|
||||
var f3f3_2 = safeConvert[int64](f3_2) * safeConvert[int64](f3)
|
||||
var f3f4_2 = safeConvert[int64](f3_2) * safeConvert[int64](f4)
|
||||
var f3f5_4 = safeConvert[int64](f3_2) * safeConvert[int64](f5_2)
|
||||
var f3f6_2 = safeConvert[int64](f3_2) * safeConvert[int64](f6)
|
||||
var f3f7_76 = safeConvert[int64](f3_2) * safeConvert[int64](f7_38)
|
||||
var f3f8_38 = safeConvert[int64](f3_2) * safeConvert[int64](f8_19)
|
||||
var f3f9_76 = safeConvert[int64](f3_2) * safeConvert[int64](f9_38)
|
||||
var f4f4 = safeConvert[int64](f4) * safeConvert[int64](f4)
|
||||
var f4f5_2 = safeConvert[int64](f4_2) * safeConvert[int64](f5)
|
||||
var f4f6_38 = safeConvert[int64](f4_2) * safeConvert[int64](f6_19)
|
||||
var f4f7_38 = safeConvert[int64](f4) * safeConvert[int64](f7_38)
|
||||
var f4f8_38 = safeConvert[int64](f4_2) * safeConvert[int64](f8_19)
|
||||
var f4f9_38 = safeConvert[int64](f4) * safeConvert[int64](f9_38)
|
||||
var f5f5_38 = safeConvert[int64](f5) * safeConvert[int64](f5_38)
|
||||
var f5f6_38 = safeConvert[int64](f5_2) * safeConvert[int64](f6_19)
|
||||
var f5f7_76 = safeConvert[int64](f5_2) * safeConvert[int64](f7_38)
|
||||
var f5f8_38 = safeConvert[int64](f5_2) * safeConvert[int64](f8_19)
|
||||
var f5f9_76 = safeConvert[int64](f5_2) * safeConvert[int64](f9_38)
|
||||
var f6f6_19 = safeConvert[int64](f6) * safeConvert[int64](f6_19)
|
||||
var f6f7_38 = safeConvert[int64](f6) * safeConvert[int64](f7_38)
|
||||
var f6f8_38 = safeConvert[int64](f6_2) * safeConvert[int64](f8_19)
|
||||
var f6f9_38 = safeConvert[int64](f6) * safeConvert[int64](f9_38)
|
||||
var f7f7_38 = safeConvert[int64](f7) * safeConvert[int64](f7_38)
|
||||
var f7f8_38 = safeConvert[int64](f7_2) * safeConvert[int64](f8_19)
|
||||
var f7f9_76 = safeConvert[int64](f7_2) * safeConvert[int64](f9_38)
|
||||
var f8f8_19 = safeConvert[int64](f8) * safeConvert[int64](f8_19)
|
||||
var f8f9_38 = safeConvert[int64](f8) * safeConvert[int64](f9_38)
|
||||
var f9f9_38 = safeConvert[int64](f9) * safeConvert[int64](f9_38)
|
||||
var
|
||||
c0, c1, c2, c3, c4, c5, c6, c7, c8, c9: int64
|
||||
h0: int64 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38
|
||||
@@ -830,7 +834,7 @@ proc geFromBytesNegateVartime(h: var GeP3, s: openArray[byte]): int32 =
|
||||
return -1;
|
||||
feMul(h.x, h.x, SqrTm1)
|
||||
|
||||
if feIsNegative(h.x) == cast[int32](s[31] shr 7):
|
||||
if feIsNegative(h.x) == safeConvert[int32](s[31] shr 7):
|
||||
feNeg(h.x, h.x)
|
||||
|
||||
feMul(h.t, h.x, h.y)
|
||||
@@ -952,14 +956,14 @@ proc equal(b, c: int8): byte =
|
||||
var ub = cast[byte](b)
|
||||
var uc = cast[byte](c)
|
||||
var x = ub xor uc
|
||||
var y = cast[uint32](x)
|
||||
var y = safeConvert[uint32](x)
|
||||
y = y - 1
|
||||
y = y shr 31
|
||||
result = cast[byte](y)
|
||||
|
||||
proc negative(b: int8): byte =
|
||||
var x = cast[uint64](b)
|
||||
x = x shr 63
|
||||
var x = cast[uint8](b)
|
||||
x = x shr 7
|
||||
result = cast[byte](x)
|
||||
|
||||
proc cmov(t: var GePrecomp, u: GePrecomp, b: byte) =
|
||||
@@ -1644,14 +1648,14 @@ proc checkScalar*(scalar: openArray[byte]): uint32 =
|
||||
c = -1
|
||||
result = NEQ(z, 0'u32) and LT0(c)
|
||||
|
||||
proc random*(t: typedesc[EdPrivateKey], rng: var BrHmacDrbgContext): EdPrivateKey =
|
||||
proc random*(t: typedesc[EdPrivateKey], rng: var HmacDrbgContext): EdPrivateKey =
|
||||
## Generate new random ED25519 private key using the given random number generator
|
||||
var
|
||||
point: GeP3
|
||||
pk: array[EdPublicKeySize, byte]
|
||||
res: EdPrivateKey
|
||||
|
||||
brHmacDrbgGenerate(addr rng, addr res.data[0], 32)
|
||||
hmacDrbgGenerate(rng, res.data.toOpenArray(0, 31))
|
||||
|
||||
var hh = sha512.digest(res.data.toOpenArray(0, 31))
|
||||
hh.data[0] = hh.data[0] and 0xF8'u8
|
||||
@@ -1663,14 +1667,14 @@ proc random*(t: typedesc[EdPrivateKey], rng: var BrHmacDrbgContext): EdPrivateKe
|
||||
|
||||
res
|
||||
|
||||
proc random*(t: typedesc[EdKeyPair], rng: var BrHmacDrbgContext): EdKeyPair =
|
||||
proc random*(t: typedesc[EdKeyPair], rng: var HmacDrbgContext): EdKeyPair =
|
||||
## Generate new random ED25519 private and public keypair using OS specific
|
||||
## CSPRNG.
|
||||
var
|
||||
point: GeP3
|
||||
res: EdKeyPair
|
||||
|
||||
brHmacDrbgGenerate(addr rng, addr res.seckey.data[0], 32)
|
||||
hmacDrbgGenerate(rng, res.seckey.data.toOpenArray(0, 31))
|
||||
|
||||
var hh = sha512.digest(res.seckey.data.toOpenArray(0, 31))
|
||||
hh.data[0] = hh.data[0] and 0xF8'u8
|
||||
|
||||
@@ -1,40 +1,33 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2020 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
# https://tools.ietf.org/html/rfc5869
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import nimcrypto
|
||||
import bearssl
|
||||
import bearssl/[kdf, hash]
|
||||
|
||||
type
|
||||
BearHKDFContext {.importc: "br_hkdf_context", header: "bearssl_kdf.h".} = object
|
||||
HKDFResult*[len: static int] = array[len, byte]
|
||||
type HkdfResult*[len: static int] = array[len, byte]
|
||||
|
||||
proc br_hkdf_init(ctx: ptr BearHKDFContext; hashClass: ptr HashClass; salt: pointer; len: csize_t) {.importc: "br_hkdf_init", header: "bearssl_kdf.h", raises: [].}
|
||||
proc br_hkdf_inject(ctx: ptr BearHKDFContext; ikm: pointer; len: csize_t) {.importc: "br_hkdf_inject", header: "bearssl_kdf.h", raises: [].}
|
||||
proc br_hkdf_flip(ctx: ptr BearHKDFContext) {.importc: "br_hkdf_flip", header: "bearssl_kdf.h", raises: [].}
|
||||
proc br_hkdf_produce(ctx: ptr BearHKDFContext; info: pointer; infoLen: csize_t; output: pointer; outputLen: csize_t) {.importc: "br_hkdf_produce", header: "bearssl_kdf.h", raises: [].}
|
||||
|
||||
proc hkdf*[T: sha256; len: static int](_: type[T]; salt, ikm, info: openArray[byte]; outputs: var openArray[HKDFResult[len]]) =
|
||||
proc hkdf*[T: sha256; len: static int](_: type[T]; salt, ikm, info: openArray[byte]; outputs: var openArray[HkdfResult[len]]) =
|
||||
var
|
||||
ctx: BearHKDFContext
|
||||
br_hkdf_init(
|
||||
addr ctx, addr sha256Vtable,
|
||||
ctx: HkdfContext
|
||||
hkdfInit(
|
||||
ctx, addr sha256Vtable,
|
||||
if salt.len > 0: unsafeAddr salt[0] else: nil, csize_t(salt.len))
|
||||
br_hkdf_inject(
|
||||
addr ctx, if ikm.len > 0: unsafeAddr ikm[0] else: nil, csize_t(ikm.len))
|
||||
br_hkdf_flip(addr ctx)
|
||||
hkdfInject(
|
||||
ctx, if ikm.len > 0: unsafeAddr ikm[0] else: nil, csize_t(ikm.len))
|
||||
hkdfFlip(ctx)
|
||||
for i in 0..outputs.high:
|
||||
br_hkdf_produce(
|
||||
addr ctx,
|
||||
discard hkdfProduce(
|
||||
ctx,
|
||||
if info.len > 0: unsafeAddr info[0]
|
||||
else: nil, csize_t(info.len),
|
||||
addr outputs[i][0], csize_t(outputs[i].len))
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements minimal ASN.1 encoding/decoding primitives.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import stew/[endians2, results, ctops]
|
||||
export results
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
import nimcrypto/utils as ncrutils
|
||||
import ../utility
|
||||
|
||||
type
|
||||
Asn1Error* {.pure.} = enum
|
||||
@@ -116,7 +117,7 @@ template toOpenArray*(af: Asn1Field): untyped =
|
||||
template isEmpty*(ab: Asn1Buffer): bool =
|
||||
ab.offset >= len(ab.buffer)
|
||||
|
||||
template isEnough*(ab: Asn1Buffer, length: int): bool =
|
||||
template isEnough*(ab: Asn1Buffer, length: int64): bool =
|
||||
len(ab.buffer) >= ab.offset + length
|
||||
|
||||
proc len*[T: Asn1Buffer|Asn1Composite](abc: T): int {.inline.} =
|
||||
@@ -341,32 +342,6 @@ proc asn1EncodeTag[T: SomeUnsignedInt](dest: var openArray[byte],
|
||||
dest[k - 1] = dest[k - 1] and 0x7F'u8
|
||||
res
|
||||
|
||||
proc asn1EncodeOid*(dest: var openArray[byte], value: openArray[int]): int =
|
||||
## Encode array of integers ``value`` as ASN.1 DER `OBJECT IDENTIFIER` and
|
||||
## return number of bytes (octets) used.
|
||||
##
|
||||
## If length of ``dest`` is less then number of required bytes to encode
|
||||
## ``value``, then result of encoding will not be stored in ``dest``
|
||||
## but number of bytes (octets) required will be returned.
|
||||
var buffer: array[16, byte]
|
||||
var res = 1
|
||||
var oidlen = 1
|
||||
for i in 2..<len(value):
|
||||
oidlen += asn1EncodeTag(buffer, cast[uint64](value[i]))
|
||||
res += asn1EncodeLength(buffer, uint64(oidlen))
|
||||
res += oidlen
|
||||
if len(dest) >= res:
|
||||
let last = dest.high
|
||||
var offset = 1
|
||||
dest[0] = Asn1Tag.Oid.code()
|
||||
offset += asn1EncodeLength(dest.toOpenArray(offset, last), uint64(oidlen))
|
||||
dest[offset] = cast[byte](value[0] * 40 + value[1])
|
||||
offset += 1
|
||||
for i in 2..<len(value):
|
||||
offset += asn1EncodeTag(dest.toOpenArray(offset, last),
|
||||
cast[uint64](value[i]))
|
||||
res
|
||||
|
||||
proc asn1EncodeOid*(dest: var openArray[byte], value: openArray[byte]): int =
|
||||
## Encode array of bytes ``value`` as ASN.1 DER `OBJECT IDENTIFIER` and return
|
||||
## number of bytes (octets) used.
|
||||
@@ -440,26 +415,29 @@ proc asn1EncodeContextTag*(dest: var openArray[byte], value: openArray[byte],
|
||||
copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value))
|
||||
res
|
||||
|
||||
proc getLength(ab: var Asn1Buffer): Asn1Result[uint64] =
|
||||
proc getLength(ab: var Asn1Buffer): Asn1Result[int] =
|
||||
## Decode length part of ASN.1 TLV triplet.
|
||||
if not ab.isEmpty():
|
||||
let b = ab.buffer[ab.offset]
|
||||
if (b and 0x80'u8) == 0x00'u8:
|
||||
let length = cast[uint64](b)
|
||||
let length = safeConvert[int](b)
|
||||
ab.offset += 1
|
||||
return ok(length)
|
||||
if b == 0x80'u8:
|
||||
return err(Asn1Error.Indefinite)
|
||||
if b == 0xFF'u8:
|
||||
return err(Asn1Error.Incorrect)
|
||||
let octets = cast[uint64](b and 0x7F'u8)
|
||||
if octets > 8'u64:
|
||||
let octets = safeConvert[int](b and 0x7F'u8)
|
||||
if octets > 8:
|
||||
return err(Asn1Error.Overflow)
|
||||
if ab.isEnough(int(octets)):
|
||||
var length: uint64 = 0
|
||||
for i in 0..<int(octets):
|
||||
length = (length shl 8) or cast[uint64](ab.buffer[ab.offset + i + 1])
|
||||
ab.offset = ab.offset + int(octets) + 1
|
||||
if ab.isEnough(octets):
|
||||
var lengthU: uint64 = 0
|
||||
for i in 0..<octets:
|
||||
lengthU = (lengthU shl 8) or safeConvert[uint64](ab.buffer[ab.offset + i + 1])
|
||||
if lengthU > uint64(int64.high):
|
||||
return err(Asn1Error.Overflow)
|
||||
let length = int(lengthU)
|
||||
ab.offset = ab.offset + octets + 1
|
||||
return ok(length)
|
||||
else:
|
||||
return err(Asn1Error.Incomplete)
|
||||
@@ -471,8 +449,8 @@ proc getTag(ab: var Asn1Buffer, tag: var int): Asn1Result[Asn1Class] =
|
||||
if not ab.isEmpty():
|
||||
let
|
||||
b = ab.buffer[ab.offset]
|
||||
c = int((b and 0xC0'u8) shr 6)
|
||||
tag = int(b and 0x3F)
|
||||
c = safeConvert[int]((b and 0xC0'u8) shr 6)
|
||||
tag = safeConvert[int](b and 0x3F)
|
||||
ab.offset += 1
|
||||
if c >= 0 and c < 4:
|
||||
ok(cast[Asn1Class](c))
|
||||
@@ -486,7 +464,7 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
var
|
||||
field: Asn1Field
|
||||
tag, ttag, offset: int
|
||||
length, tlength: uint64
|
||||
length, tlength: int
|
||||
aclass: Asn1Class
|
||||
inclass: bool
|
||||
|
||||
@@ -516,7 +494,7 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
if length != 1:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
let b = ab.buffer[ab.offset]
|
||||
@@ -524,9 +502,8 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.Boolean, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: 1)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
index: ttag, offset: ab.offset,
|
||||
length: 1, buffer: ab.buffer)
|
||||
field.vbool = (b == 0xFF'u8)
|
||||
ab.offset += 1
|
||||
return ok(field)
|
||||
@@ -536,12 +513,12 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
if length == 0:
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
# Count number of leading zeroes
|
||||
var zc = 0
|
||||
while (zc < int(length)) and (ab.buffer[ab.offset + zc] == 0x00'u8):
|
||||
while (zc < length) and (ab.buffer[ab.offset + zc] == 0x00'u8):
|
||||
inc(zc)
|
||||
|
||||
if zc > 1:
|
||||
@@ -550,47 +527,45 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
if zc == 0:
|
||||
# Negative or Positive integer
|
||||
field = Asn1Field(kind: Asn1Tag.Integer, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
index: ttag, offset: ab.offset,
|
||||
length: length, buffer: ab.buffer)
|
||||
if (ab.buffer[ab.offset] and 0x80'u8) == 0x80'u8:
|
||||
# Negative integer
|
||||
if length <= 8:
|
||||
# We need this transformation because our field.vint is uint64.
|
||||
for i in 0 ..< 8:
|
||||
if i < 8 - int(length):
|
||||
if i < 8 - length:
|
||||
field.vint = (field.vint shl 8) or 0xFF'u64
|
||||
else:
|
||||
let offset = ab.offset + i - (8 - int(length))
|
||||
field.vint = (field.vint shl 8) or uint64(ab.buffer[offset])
|
||||
let offset = ab.offset + i - (8 - length)
|
||||
field.vint = (field.vint shl 8) or safeConvert[uint64](ab.buffer[offset])
|
||||
else:
|
||||
# Positive integer
|
||||
if length <= 8:
|
||||
for i in 0 ..< int(length):
|
||||
for i in 0 ..< length:
|
||||
field.vint = (field.vint shl 8) or
|
||||
uint64(ab.buffer[ab.offset + i])
|
||||
ab.offset += int(length)
|
||||
safeConvert[uint64](ab.buffer[ab.offset + i])
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
else:
|
||||
if length == 1:
|
||||
# Zero value integer
|
||||
field = Asn1Field(kind: Asn1Tag.Integer, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length), vint: 0'u64)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
index: ttag, offset: ab.offset,
|
||||
length: length, vint: 0'u64,
|
||||
buffer: ab.buffer)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
else:
|
||||
# Positive integer with leading zero
|
||||
field = Asn1Field(kind: Asn1Tag.Integer, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset) + 1,
|
||||
length: int(length) - 1)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
index: ttag, offset: ab.offset + 1,
|
||||
length: length - 1, buffer: ab.buffer)
|
||||
if length <= 9:
|
||||
for i in 1 ..< int(length):
|
||||
for i in 1 ..< length:
|
||||
field.vint = (field.vint shl 8) or
|
||||
uint64(ab.buffer[ab.offset + i])
|
||||
ab.offset += int(length)
|
||||
safeConvert[uint64](ab.buffer[ab.offset + i])
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.BitString.code():
|
||||
@@ -606,14 +581,13 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
else:
|
||||
# Zero-length BIT STRING.
|
||||
field = Asn1Field(kind: Asn1Tag.BitString, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset + 1),
|
||||
length: 0, ubits: 0)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
index: ttag, offset: ab.offset + 1,
|
||||
length: 0, ubits: 0, buffer: ab.buffer)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
else:
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
let unused = ab.buffer[ab.offset]
|
||||
@@ -621,28 +595,27 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
# Number of unused bits should not be bigger then `7`.
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
let mask = (1'u8 shl int(unused)) - 1'u8
|
||||
if (ab.buffer[ab.offset + int(length) - 1] and mask) != 0x00'u8:
|
||||
let mask = (1'u8 shl safeConvert[int](unused)) - 1'u8
|
||||
if (ab.buffer[ab.offset + length - 1] and mask) != 0x00'u8:
|
||||
## All unused bits should be set to `0`.
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.BitString, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset + 1),
|
||||
length: int(length - 1), ubits: int(unused))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
index: ttag, offset: ab.offset + 1,
|
||||
length: length - 1, ubits: safeConvert[int](unused),
|
||||
buffer: ab.buffer)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.OctetString.code():
|
||||
# OCTET STRING
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.OctetString, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
index: ttag, offset: ab.offset,
|
||||
length: length, buffer: ab.buffer)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.Null.code():
|
||||
@@ -651,33 +624,30 @@ proc read*(ab: var Asn1Buffer): Asn1Result[Asn1Field] =
|
||||
return err(Asn1Error.Incorrect)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.Null, klass: aclass, index: ttag,
|
||||
offset: int(ab.offset), length: 0)
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
offset: ab.offset, length: 0, buffer: ab.buffer)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.Oid.code():
|
||||
# OID
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.Oid, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
index: ttag, offset: ab.offset,
|
||||
length: length, buffer: ab.buffer)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
of Asn1Tag.Sequence.code():
|
||||
# SEQUENCE
|
||||
if not ab.isEnough(int(length)):
|
||||
if not ab.isEnough(length):
|
||||
return err(Asn1Error.Incomplete)
|
||||
|
||||
field = Asn1Field(kind: Asn1Tag.Sequence, klass: aclass,
|
||||
index: ttag, offset: int(ab.offset),
|
||||
length: int(length))
|
||||
shallowCopy(field.buffer, ab.buffer)
|
||||
ab.offset += int(length)
|
||||
index: ttag, offset: ab.offset,
|
||||
length: length, buffer: ab.buffer)
|
||||
ab.offset += length
|
||||
return ok(field)
|
||||
|
||||
else:
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements constant-time RSA PKCS#1.5 DSA.
|
||||
##
|
||||
@@ -13,8 +13,9 @@
|
||||
## BearSSL library <https://bearssl.org/>
|
||||
## Copyright(C) 2018 Thomas Pornin <pornin@bolet.org>.
|
||||
|
||||
{.push raises: Defect.}
|
||||
import bearssl
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl/[rsa, rand, hash]
|
||||
import minasn1
|
||||
import stew/[results, ctops]
|
||||
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
||||
@@ -33,41 +34,41 @@ const
|
||||
## Default RSA key size in bits.
|
||||
|
||||
RsaOidSha1* = [
|
||||
0x05'u8, 0x2B'u8, 0x0E'u8, 0x03'u8, 0x02'u8, 0x1A'u8
|
||||
byte 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A
|
||||
]
|
||||
## RSA PKCS#1.5 SHA-1 hash object identifier.
|
||||
RsaOidSha224* = [
|
||||
0x09'u8, 0x60'u8, 0x86'u8, 0x48'u8, 0x01'u8, 0x65'u8, 0x03'u8, 0x04'u8,
|
||||
0x02'u8, 0x04'u8
|
||||
byte 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
|
||||
0x02, 0x04
|
||||
]
|
||||
## RSA PKCS#1.5 SHA-224 hash object identifier.
|
||||
RsaOidSha256* = [
|
||||
0x09'u8, 0x60'u8, 0x86'u8, 0x48'u8, 0x01'u8, 0x65'u8, 0x03'u8, 0x04'u8,
|
||||
0x02'u8, 0x01'u8
|
||||
byte 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
|
||||
0x02, 0x01
|
||||
]
|
||||
## RSA PKCS#1.5 SHA-256 hash object identifier.
|
||||
RsaOidSha384* = [
|
||||
0x09'u8, 0x60'u8, 0x86'u8, 0x48'u8, 0x01'u8, 0x65'u8, 0x03'u8, 0x04'u8,
|
||||
0x02'u8, 0x02'u8
|
||||
byte 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
|
||||
0x02, 0x02
|
||||
]
|
||||
## RSA PKCS#1.5 SHA-384 hash object identifier.
|
||||
RsaOidSha512* = [
|
||||
0x09'u8, 0x60'u8, 0x86'u8, 0x48'u8, 0x01'u8, 0x65'u8, 0x03'u8, 0x04'u8,
|
||||
0x02'u8, 0x03'u8
|
||||
byte 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
|
||||
0x02, 0x03
|
||||
]
|
||||
## RSA PKCS#1.5 SHA-512 hash object identifier.
|
||||
|
||||
type
|
||||
RsaPrivateKey* = ref object
|
||||
buffer*: seq[byte]
|
||||
seck*: BrRsaPrivateKey
|
||||
pubk*: BrRsaPublicKey
|
||||
pexp*: ptr char
|
||||
pexplen*: int
|
||||
seck*: rsa.RsaPrivateKey
|
||||
pubk*: rsa.RsaPublicKey
|
||||
pexp*: ptr byte
|
||||
pexplen*: uint
|
||||
|
||||
RsaPublicKey* = ref object
|
||||
buffer*: seq[byte]
|
||||
key*: BrRsaPublicKey
|
||||
key*: rsa.RsaPublicKey
|
||||
|
||||
RsaKeyPair* = RsaPrivateKey
|
||||
|
||||
@@ -99,8 +100,8 @@ template getFinish(bs, os, ls: untyped): untyped =
|
||||
var eo = -1
|
||||
if p >= s:
|
||||
let so = cast[int](p - s)
|
||||
if so + ls <= len(bs):
|
||||
eo = so + ls - 1
|
||||
if so + int(ls) <= len(bs):
|
||||
eo = so + int(ls) - 1
|
||||
eo
|
||||
|
||||
template getArray*(bs, os, ls: untyped): untyped =
|
||||
@@ -109,12 +110,12 @@ template getArray*(bs, os, ls: untyped): untyped =
|
||||
template trimZeroes(b: seq[byte], pt, ptlen: untyped) =
|
||||
var length = ptlen
|
||||
for i in 0..<length:
|
||||
if pt[] != cast[char](0x00'u8):
|
||||
if pt[] != byte(0x00):
|
||||
break
|
||||
pt = cast[ptr char](cast[uint](pt) + 1)
|
||||
pt = cast[ptr byte](cast[uint](pt) + 1)
|
||||
ptlen -= 1
|
||||
|
||||
proc random*[T: RsaKP](t: typedesc[T], rng: var BrHmacDrbgContext,
|
||||
proc random*[T: RsaKP](t: typedesc[T], rng: var HmacDrbgContext,
|
||||
bits = DefaultKeySize,
|
||||
pubexp = DefaultPublicExponent): RsaResult[T] =
|
||||
## Generate new random RSA private key using BearSSL's HMAC-SHA256-DRBG
|
||||
@@ -129,14 +130,14 @@ proc random*[T: RsaKP](t: typedesc[T], rng: var BrHmacDrbgContext,
|
||||
|
||||
let
|
||||
sko = 0
|
||||
pko = brRsaPrivateKeyBufferSize(bits)
|
||||
eko = pko + brRsaPublicKeyBufferSize(bits)
|
||||
pko = rsaKbufPrivSize(bits)
|
||||
eko = pko + rsaKbufPubSize(bits)
|
||||
length = eko + ((bits + 7) shr 3)
|
||||
|
||||
let res = new T
|
||||
res.buffer = newSeq[byte](length)
|
||||
|
||||
var keygen = brRsaKeygenGetDefault()
|
||||
var keygen = rsaKeygenGetDefault()
|
||||
|
||||
if keygen(addr rng.vtable,
|
||||
addr res.seck, addr res.buffer[sko],
|
||||
@@ -145,12 +146,12 @@ proc random*[T: RsaKP](t: typedesc[T], rng: var BrHmacDrbgContext,
|
||||
return err(RsaGenError)
|
||||
|
||||
let
|
||||
compute = brRsaComputePrivexpGetDefault()
|
||||
compute = rsaComputePrivexpGetDefault()
|
||||
computed = compute(addr res.buffer[eko], addr res.seck, pubexp)
|
||||
if computed == 0:
|
||||
return err(RsaGenError)
|
||||
|
||||
res.pexp = cast[ptr char](addr res.buffer[eko])
|
||||
res.pexp = addr res.buffer[eko]
|
||||
res.pexplen = computed
|
||||
|
||||
trimZeroes(res.buffer, res.seck.p, res.seck.plen)
|
||||
@@ -169,12 +170,12 @@ proc copy*[T: RsaPKI](key: T): T =
|
||||
doAssert(not isNil(key))
|
||||
when T is RsaPrivateKey:
|
||||
if len(key.buffer) > 0:
|
||||
let length = key.seck.plen + key.seck.qlen + key.seck.dplen +
|
||||
key.seck.dqlen + key.seck.iqlen + key.pubk.nlen +
|
||||
key.pubk.elen + key.pexplen
|
||||
let length = key.seck.plen.uint + key.seck.qlen.uint + key.seck.dplen.uint +
|
||||
key.seck.dqlen.uint + key.seck.iqlen.uint + key.pubk.nlen.uint +
|
||||
key.pubk.elen.uint + key.pexplen.uint
|
||||
result = new RsaPrivateKey
|
||||
result.buffer = newSeq[byte](length)
|
||||
let po = 0
|
||||
let po: uint = 0
|
||||
let qo = po + key.seck.plen
|
||||
let dpo = qo + key.seck.qlen
|
||||
let dqo = dpo + key.seck.dplen
|
||||
@@ -190,14 +191,14 @@ proc copy*[T: RsaPKI](key: T): T =
|
||||
copyMem(addr result.buffer[no], key.pubk.n, key.pubk.nlen)
|
||||
copyMem(addr result.buffer[eo], key.pubk.e, key.pubk.elen)
|
||||
copyMem(addr result.buffer[peo], key.pexp, key.pexplen)
|
||||
result.seck.p = cast[ptr char](addr result.buffer[po])
|
||||
result.seck.q = cast[ptr char](addr result.buffer[qo])
|
||||
result.seck.dp = cast[ptr char](addr result.buffer[dpo])
|
||||
result.seck.dq = cast[ptr char](addr result.buffer[dqo])
|
||||
result.seck.iq = cast[ptr char](addr result.buffer[iqo])
|
||||
result.pubk.n = cast[ptr char](addr result.buffer[no])
|
||||
result.pubk.e = cast[ptr char](addr result.buffer[eo])
|
||||
result.pexp = cast[ptr char](addr result.buffer[peo])
|
||||
result.seck.p = addr result.buffer[po]
|
||||
result.seck.q = addr result.buffer[qo]
|
||||
result.seck.dp = addr result.buffer[dpo]
|
||||
result.seck.dq = addr result.buffer[dqo]
|
||||
result.seck.iq = addr result.buffer[iqo]
|
||||
result.pubk.n = addr result.buffer[no]
|
||||
result.pubk.e = addr result.buffer[eo]
|
||||
result.pexp = addr result.buffer[peo]
|
||||
result.seck.plen = key.seck.plen
|
||||
result.seck.qlen = key.seck.qlen
|
||||
result.seck.dplen = key.seck.dplen
|
||||
@@ -231,8 +232,8 @@ proc getPublicKey*(key: RsaPrivateKey): RsaPublicKey =
|
||||
let length = key.pubk.nlen + key.pubk.elen
|
||||
result = new RsaPublicKey
|
||||
result.buffer = newSeq[byte](length)
|
||||
result.key.n = cast[ptr char](addr result.buffer[0])
|
||||
result.key.e = cast[ptr char](addr result.buffer[key.pubk.nlen])
|
||||
result.key.n = addr result.buffer[0]
|
||||
result.key.e = addr result.buffer[key.pubk.nlen]
|
||||
copyMem(addr result.buffer[0], cast[pointer](key.pubk.n), key.pubk.nlen)
|
||||
copyMem(addr result.buffer[key.pubk.nlen], cast[pointer](key.pubk.e),
|
||||
key.pubk.elen)
|
||||
@@ -472,22 +473,22 @@ proc init*(key: var RsaPrivateKey, data: openArray[byte]): Result[void, Asn1Erro
|
||||
len(rawdp) > 0 and len(rawdq) > 0 and len(rawiq) > 0:
|
||||
key = new RsaPrivateKey
|
||||
key.buffer = @data
|
||||
key.pubk.n = cast[ptr char](addr key.buffer[rawn.offset])
|
||||
key.pubk.e = cast[ptr char](addr key.buffer[rawpube.offset])
|
||||
key.seck.p = cast[ptr char](addr key.buffer[rawp.offset])
|
||||
key.seck.q = cast[ptr char](addr key.buffer[rawq.offset])
|
||||
key.seck.dp = cast[ptr char](addr key.buffer[rawdp.offset])
|
||||
key.seck.dq = cast[ptr char](addr key.buffer[rawdq.offset])
|
||||
key.seck.iq = cast[ptr char](addr key.buffer[rawiq.offset])
|
||||
key.pexp = cast[ptr char](addr key.buffer[rawprie.offset])
|
||||
key.pubk.nlen = len(rawn)
|
||||
key.pubk.elen = len(rawpube)
|
||||
key.seck.plen = len(rawp)
|
||||
key.seck.qlen = len(rawq)
|
||||
key.seck.dplen = len(rawdp)
|
||||
key.seck.dqlen = len(rawdq)
|
||||
key.seck.iqlen = len(rawiq)
|
||||
key.pexplen = len(rawprie)
|
||||
key.pubk.n = addr key.buffer[rawn.offset]
|
||||
key.pubk.e = addr key.buffer[rawpube.offset]
|
||||
key.seck.p = addr key.buffer[rawp.offset]
|
||||
key.seck.q = addr key.buffer[rawq.offset]
|
||||
key.seck.dp = addr key.buffer[rawdp.offset]
|
||||
key.seck.dq = addr key.buffer[rawdq.offset]
|
||||
key.seck.iq = addr key.buffer[rawiq.offset]
|
||||
key.pexp = addr key.buffer[rawprie.offset]
|
||||
key.pubk.nlen = uint(len(rawn))
|
||||
key.pubk.elen = uint(len(rawpube))
|
||||
key.seck.plen = uint(len(rawp))
|
||||
key.seck.qlen = uint(len(rawq))
|
||||
key.seck.dplen = uint(len(rawdp))
|
||||
key.seck.dqlen = uint(len(rawdq))
|
||||
key.seck.iqlen = uint(len(rawiq))
|
||||
key.pexplen = uint(len(rawprie))
|
||||
key.seck.nBitlen = cast[uint32](len(rawn) shl 3)
|
||||
ok()
|
||||
else:
|
||||
@@ -554,10 +555,10 @@ proc init*(key: var RsaPublicKey, data: openArray[byte]): Result[void, Asn1Error
|
||||
if len(rawn) >= (MinKeySize shr 3) and len(rawe) > 0:
|
||||
key = new RsaPublicKey
|
||||
key.buffer = @data
|
||||
key.key.n = cast[ptr char](addr key.buffer[rawn.offset])
|
||||
key.key.e = cast[ptr char](addr key.buffer[rawe.offset])
|
||||
key.key.nlen = len(rawn)
|
||||
key.key.elen = len(rawe)
|
||||
key.key.n = addr key.buffer[rawn.offset]
|
||||
key.key.e = addr key.buffer[rawe.offset]
|
||||
key.key.nlen = uint(len(rawn))
|
||||
key.key.elen = uint(len(rawe))
|
||||
ok()
|
||||
else:
|
||||
err(Asn1Error.Incorrect)
|
||||
@@ -682,7 +683,7 @@ proc `==`*(a, b: RsaPrivateKey): bool =
|
||||
false
|
||||
else:
|
||||
if a.seck.nBitlen == b.seck.nBitlen:
|
||||
if cast[int](a.seck.nBitlen) > 0:
|
||||
if a.seck.nBitlen > 0'u:
|
||||
let r1 = CT.isEqual(getArray(a.buffer, a.seck.p, a.seck.plen),
|
||||
getArray(b.buffer, b.seck.p, b.seck.plen))
|
||||
let r2 = CT.isEqual(getArray(a.buffer, a.seck.q, a.seck.qlen),
|
||||
@@ -749,22 +750,22 @@ proc sign*[T: byte|char](key: RsaPrivateKey,
|
||||
if isNil(key):
|
||||
return err(RsaKeyIncorrectError)
|
||||
|
||||
var hc: BrHashCompatContext
|
||||
var hc: HashCompatContext
|
||||
var hash: array[32, byte]
|
||||
let impl = BrRsaPkcs1SignGetDefault()
|
||||
let impl = rsaPkcs1SignGetDefault()
|
||||
var res = new RsaSignature
|
||||
res.buffer = newSeq[byte]((key.seck.nBitlen + 7) shr 3)
|
||||
var kv = addr sha256Vtable
|
||||
kv.init(addr hc.vtable)
|
||||
if len(message) > 0:
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], len(message))
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
|
||||
else:
|
||||
kv.update(addr hc.vtable, nil, 0)
|
||||
kv.output(addr hc.vtable, addr hash[0])
|
||||
kv.out(addr hc.vtable, addr hash[0])
|
||||
var oid = RsaOidSha256
|
||||
let implRes = impl(cast[ptr char](addr oid[0]),
|
||||
cast[ptr char](addr hash[0]), len(hash),
|
||||
addr key.seck, cast[ptr char](addr res.buffer[0]))
|
||||
let implRes = impl(addr oid[0],
|
||||
addr hash[0], uint(len(hash)),
|
||||
addr key.seck, addr res.buffer[0])
|
||||
if implRes == 0:
|
||||
err(RsaSignatureError)
|
||||
else:
|
||||
@@ -779,20 +780,20 @@ proc verify*[T: byte|char](sig: RsaSignature, message: openArray[T],
|
||||
## verification failed.
|
||||
doAssert((not isNil(sig)) and (not isNil(pubkey)))
|
||||
if len(sig.buffer) > 0:
|
||||
var hc: BrHashCompatContext
|
||||
var hc: HashCompatContext
|
||||
var hash: array[32, byte]
|
||||
var check: array[32, byte]
|
||||
var impl = BrRsaPkcs1VrfyGetDefault()
|
||||
var impl = rsaPkcs1VrfyGetDefault()
|
||||
var kv = addr sha256Vtable
|
||||
kv.init(addr hc.vtable)
|
||||
if len(message) > 0:
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], len(message))
|
||||
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
|
||||
else:
|
||||
kv.update(addr hc.vtable, nil, 0)
|
||||
kv.output(addr hc.vtable, addr hash[0])
|
||||
kv.out(addr hc.vtable, addr hash[0])
|
||||
var oid = RsaOidSha256
|
||||
let res = impl(cast[ptr char](addr sig.buffer[0]), len(sig.buffer),
|
||||
cast[ptr char](addr oid[0]),
|
||||
len(check), addr pubkey.key, cast[ptr char](addr check[0]))
|
||||
let res = impl(addr sig.buffer[0], uint(len(sig.buffer)),
|
||||
addr oid[0],
|
||||
uint(len(check)), addr pubkey.key, addr check[0])
|
||||
if res == 1:
|
||||
result = equalMem(addr check[0], addr hash[0], len(hash))
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl/rand
|
||||
import
|
||||
secp256k1, bearssl,
|
||||
secp256k1,
|
||||
stew/[byteutils, results],
|
||||
nimcrypto/[hash, sha2]
|
||||
|
||||
export sha2, results
|
||||
export sha2, results, rand
|
||||
|
||||
const
|
||||
SkRawPrivateKeySize* = 256 div 8
|
||||
@@ -31,20 +32,18 @@ type
|
||||
SkSignature* = distinct secp256k1.SkSignature
|
||||
SkKeyPair* = distinct secp256k1.SkKeyPair
|
||||
|
||||
template pubkey*(v: SkKeyPair): SkPublicKey = SkPublicKey(secp256k1.SkKeyPair(v).pubkey)
|
||||
template seckey*(v: SkKeyPair): SkPrivateKey = SkPrivateKey(secp256k1.SkKeyPair(v).seckey)
|
||||
|
||||
proc random*(t: typedesc[SkPrivateKey], rng: var BrHmacDrbgContext): SkPrivateKey =
|
||||
let rngPtr = unsafeAddr rng # doesn't escape
|
||||
proc random*(t: typedesc[SkPrivateKey], rng: var HmacDrbgContext): SkPrivateKey =
|
||||
#TODO is there a better way?
|
||||
var rngPtr = addr rng
|
||||
proc callRng(data: var openArray[byte]) =
|
||||
brHmacDrbgGenerate(rngPtr[], data)
|
||||
hmacDrbgGenerate(rngPtr[], data)
|
||||
|
||||
SkPrivateKey(SkSecretKey.random(callRng))
|
||||
|
||||
proc random*(t: typedesc[SkKeyPair], rng: var BrHmacDrbgContext): SkKeyPair =
|
||||
let rngPtr = unsafeAddr rng # doesn't escape
|
||||
proc random*(t: typedesc[SkKeyPair], rng: var HmacDrbgContext): SkKeyPair =
|
||||
let rngPtr = addr rng
|
||||
proc callRng(data: var openArray[byte]) =
|
||||
brHmacDrbgGenerate(rngPtr[], data)
|
||||
hmacDrbgGenerate(rngPtr[], data)
|
||||
|
||||
SkKeyPair(secp256k1.SkKeyPair.random(callRng))
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
## This module implementes API for `go-libp2p-daemon`.
|
||||
import std/[os, osproc, strutils, tables, strtabs, sequtils]
|
||||
import pkg/[chronos, chronicles]
|
||||
import ../varint, ../multiaddress, ../multicodec, ../cid, ../peerid
|
||||
import ../wire, ../multihash, ../protobuf/minprotobuf, ../errors
|
||||
import ../crypto/crypto
|
||||
import ../crypto/crypto, ../utility
|
||||
|
||||
export
|
||||
peerid, multiaddress, multicodec, multihash, cid, crypto, wire, errors
|
||||
@@ -150,10 +150,10 @@ type
|
||||
key*: PublicKey
|
||||
|
||||
P2PStreamCallback* = proc(api: DaemonAPI,
|
||||
stream: P2PStream): Future[void] {.gcsafe, raises: [Defect, CatchableError].}
|
||||
stream: P2PStream): Future[void] {.gcsafe, raises: [CatchableError].}
|
||||
P2PPubSubCallback* = proc(api: DaemonAPI,
|
||||
ticket: PubsubTicket,
|
||||
message: PubSubMessage): Future[bool] {.gcsafe, raises: [Defect, CatchableError].}
|
||||
message: PubSubMessage): Future[bool] {.gcsafe, raises: [CatchableError].}
|
||||
|
||||
DaemonError* = object of LPError
|
||||
DaemonRemoteError* = object of DaemonError
|
||||
@@ -167,7 +167,7 @@ proc requestIdentity(): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/conn.go
|
||||
## Processing function `doIdentify(req *pb.Request)`.
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
result.write(1, cast[uint](RequestType.IDENTIFY))
|
||||
result.write(1, safeConvert[uint](RequestType.IDENTIFY))
|
||||
result.finish()
|
||||
|
||||
proc requestConnect(peerid: PeerId,
|
||||
@@ -182,7 +182,7 @@ proc requestConnect(peerid: PeerId,
|
||||
msg.write(2, item.data.buffer)
|
||||
if timeout > 0:
|
||||
msg.write(3, hint64(timeout))
|
||||
result.write(1, cast[uint](RequestType.CONNECT))
|
||||
result.write(1, safeConvert[uint](RequestType.CONNECT))
|
||||
result.write(2, msg)
|
||||
result.finish()
|
||||
|
||||
@@ -192,7 +192,7 @@ proc requestDisconnect(peerid: PeerId): ProtoBuffer =
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, peerid)
|
||||
result.write(1, cast[uint](RequestType.DISCONNECT))
|
||||
result.write(1, safeConvert[uint](RequestType.DISCONNECT))
|
||||
result.write(7, msg)
|
||||
result.finish()
|
||||
|
||||
@@ -208,7 +208,7 @@ proc requestStreamOpen(peerid: PeerId,
|
||||
msg.write(2, item)
|
||||
if timeout > 0:
|
||||
msg.write(3, hint64(timeout))
|
||||
result.write(1, cast[uint](RequestType.STREAM_OPEN))
|
||||
result.write(1, safeConvert[uint](RequestType.STREAM_OPEN))
|
||||
result.write(3, msg)
|
||||
result.finish()
|
||||
|
||||
@@ -221,7 +221,7 @@ proc requestStreamHandler(address: MultiAddress,
|
||||
msg.write(1, address.data.buffer)
|
||||
for item in protocols:
|
||||
msg.write(2, item)
|
||||
result.write(1, cast[uint](RequestType.STREAM_HANDLER))
|
||||
result.write(1, safeConvert[uint](RequestType.STREAM_HANDLER))
|
||||
result.write(4, msg)
|
||||
result.finish()
|
||||
|
||||
@@ -229,13 +229,13 @@ proc requestListPeers(): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/conn.go
|
||||
## Processing function `doListPeers(req *pb.Request)`
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
result.write(1, cast[uint](RequestType.LIST_PEERS))
|
||||
result.write(1, safeConvert[uint](RequestType.LIST_PEERS))
|
||||
result.finish()
|
||||
|
||||
proc requestDHTFindPeer(peer: PeerId, timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTFindPeer(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.FIND_PEER)
|
||||
let msgid = safeConvert[uint](DHTRequestType.FIND_PEER)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -243,7 +243,7 @@ proc requestDHTFindPeer(peer: PeerId, timeout = 0): ProtoBuffer =
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, safeConvert[uint](RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
@@ -251,7 +251,7 @@ proc requestDHTFindPeersConnectedToPeer(peer: PeerId,
|
||||
timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTFindPeersConnectedToPeer(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.FIND_PEERS_CONNECTED_TO_PEER)
|
||||
let msgid = safeConvert[uint](DHTRequestType.FIND_PEERS_CONNECTED_TO_PEER)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -259,7 +259,7 @@ proc requestDHTFindPeersConnectedToPeer(peer: PeerId,
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, safeConvert[uint](RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
@@ -267,7 +267,7 @@ proc requestDHTFindProviders(cid: Cid,
|
||||
count: uint32, timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTFindProviders(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.FIND_PROVIDERS)
|
||||
let msgid = safeConvert[uint](DHTRequestType.FIND_PROVIDERS)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -276,14 +276,14 @@ proc requestDHTFindProviders(cid: Cid,
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, safeConvert[uint](RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestDHTGetClosestPeers(key: string, timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTGetClosestPeers(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.GET_CLOSEST_PEERS)
|
||||
let msgid = safeConvert[uint](DHTRequestType.GET_CLOSEST_PEERS)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -291,14 +291,14 @@ proc requestDHTGetClosestPeers(key: string, timeout = 0): ProtoBuffer =
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, safeConvert[uint](RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestDHTGetPublicKey(peer: PeerId, timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTGetPublicKey(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.GET_PUBLIC_KEY)
|
||||
let msgid = safeConvert[uint](DHTRequestType.GET_PUBLIC_KEY)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -306,14 +306,14 @@ proc requestDHTGetPublicKey(peer: PeerId, timeout = 0): ProtoBuffer =
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, safeConvert[uint](RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestDHTGetValue(key: string, timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTGetValue(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.GET_VALUE)
|
||||
let msgid = safeConvert[uint](DHTRequestType.GET_VALUE)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -321,14 +321,14 @@ proc requestDHTGetValue(key: string, timeout = 0): ProtoBuffer =
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, safeConvert[uint](RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestDHTSearchValue(key: string, timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTSearchValue(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.SEARCH_VALUE)
|
||||
let msgid = safeConvert[uint](DHTRequestType.SEARCH_VALUE)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -336,7 +336,7 @@ proc requestDHTSearchValue(key: string, timeout = 0): ProtoBuffer =
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, safeConvert[uint](RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
@@ -344,7 +344,7 @@ proc requestDHTPutValue(key: string, value: openArray[byte],
|
||||
timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTPutValue(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.PUT_VALUE)
|
||||
let msgid = safeConvert[uint](DHTRequestType.PUT_VALUE)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -353,14 +353,14 @@ proc requestDHTPutValue(key: string, value: openArray[byte],
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, uint(RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestDHTProvide(cid: Cid, timeout = 0): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go
|
||||
## Processing function `doDHTProvide(req *pb.DHTRequest)`.
|
||||
let msgid = cast[uint](DHTRequestType.PROVIDE)
|
||||
let msgid = safeConvert[uint](DHTRequestType.PROVIDE)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -368,13 +368,13 @@ proc requestDHTProvide(cid: Cid, timeout = 0): ProtoBuffer =
|
||||
if timeout > 0:
|
||||
msg.write(7, hint64(timeout))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.DHT))
|
||||
result.write(1, safeConvert[uint](RequestType.DHT))
|
||||
result.write(5, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestCMTagPeer(peer: PeerId, tag: string, weight: int): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/connmgr.go#L18
|
||||
let msgid = cast[uint](ConnManagerRequestType.TAG_PEER)
|
||||
let msgid = safeConvert[uint](ConnManagerRequestType.TAG_PEER)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
@@ -382,83 +382,83 @@ proc requestCMTagPeer(peer: PeerId, tag: string, weight: int): ProtoBuffer =
|
||||
msg.write(3, tag)
|
||||
msg.write(4, hint64(weight))
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.CONNMANAGER))
|
||||
result.write(1, safeConvert[uint](RequestType.CONNMANAGER))
|
||||
result.write(6, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestCMUntagPeer(peer: PeerId, tag: string): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/connmgr.go#L33
|
||||
let msgid = cast[uint](ConnManagerRequestType.UNTAG_PEER)
|
||||
let msgid = safeConvert[uint](ConnManagerRequestType.UNTAG_PEER)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
msg.write(2, peer)
|
||||
msg.write(3, tag)
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.CONNMANAGER))
|
||||
result.write(1, safeConvert[uint](RequestType.CONNMANAGER))
|
||||
result.write(6, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestCMTrim(): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/connmgr.go#L47
|
||||
let msgid = cast[uint](ConnManagerRequestType.TRIM)
|
||||
let msgid = safeConvert[uint](ConnManagerRequestType.TRIM)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.CONNMANAGER))
|
||||
result.write(1, safeConvert[uint](RequestType.CONNMANAGER))
|
||||
result.write(6, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestPSGetTopics(): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/pubsub.go
|
||||
## Processing function `doPubsubGetTopics(req *pb.PSRequest)`.
|
||||
let msgid = cast[uint](PSRequestType.GET_TOPICS)
|
||||
let msgid = safeConvert[uint](PSRequestType.GET_TOPICS)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.PUBSUB))
|
||||
result.write(1, safeConvert[uint](RequestType.PUBSUB))
|
||||
result.write(8, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestPSListPeers(topic: string): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/pubsub.go
|
||||
## Processing function `doPubsubListPeers(req *pb.PSRequest)`.
|
||||
let msgid = cast[uint](PSRequestType.LIST_PEERS)
|
||||
let msgid = safeConvert[uint](PSRequestType.LIST_PEERS)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
msg.write(2, topic)
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.PUBSUB))
|
||||
result.write(1, safeConvert[uint](RequestType.PUBSUB))
|
||||
result.write(8, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestPSPublish(topic: string, data: openArray[byte]): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/pubsub.go
|
||||
## Processing function `doPubsubPublish(req *pb.PSRequest)`.
|
||||
let msgid = cast[uint](PSRequestType.PUBLISH)
|
||||
let msgid = safeConvert[uint](PSRequestType.PUBLISH)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
msg.write(2, topic)
|
||||
msg.write(3, data)
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.PUBSUB))
|
||||
result.write(1, safeConvert[uint](RequestType.PUBSUB))
|
||||
result.write(8, msg)
|
||||
result.finish()
|
||||
|
||||
proc requestPSSubscribe(topic: string): ProtoBuffer =
|
||||
## https://github.com/libp2p/go-libp2p-daemon/blob/master/pubsub.go
|
||||
## Processing function `doPubsubSubscribe(req *pb.PSRequest)`.
|
||||
let msgid = cast[uint](PSRequestType.SUBSCRIBE)
|
||||
let msgid = safeConvert[uint](PSRequestType.SUBSCRIBE)
|
||||
result = initProtoBuffer({WithVarintLength})
|
||||
var msg = initProtoBuffer()
|
||||
msg.write(1, msgid)
|
||||
msg.write(2, topic)
|
||||
msg.finish()
|
||||
result.write(1, cast[uint](RequestType.PUBSUB))
|
||||
result.write(1, safeConvert[uint](RequestType.PUBSUB))
|
||||
result.write(8, msg)
|
||||
result.finish()
|
||||
|
||||
@@ -471,7 +471,7 @@ proc checkResponse(pb: ProtoBuffer): ResponseKind {.inline.} =
|
||||
else:
|
||||
result = ResponseKind.Error
|
||||
|
||||
proc getErrorMessage(pb: ProtoBuffer): string {.inline, raises: [Defect, DaemonLocalError].} =
|
||||
proc getErrorMessage(pb: ProtoBuffer): string {.inline, raises: [DaemonLocalError].} =
|
||||
var error: seq[byte]
|
||||
if pb.getRequiredField(ResponseType.ERROR.int, error).isOk():
|
||||
if initProtoBuffer(error).getRequiredField(1, result).isErr():
|
||||
@@ -489,7 +489,7 @@ proc recvMessage(conn: StreamTransport): Future[seq[byte]] {.async.} =
|
||||
res = PB.getUVarint(buffer.toOpenArray(0, i), length, size)
|
||||
if res.isOk():
|
||||
break
|
||||
if res.isErr() or size > MaxMessageSize:
|
||||
if res.isErr() or size > 1'u shl 22:
|
||||
buffer.setLen(0)
|
||||
result = buffer
|
||||
return
|
||||
@@ -501,7 +501,7 @@ proc recvMessage(conn: StreamTransport): Future[seq[byte]] {.async.} =
|
||||
result = buffer
|
||||
|
||||
proc newConnection*(api: DaemonAPI): Future[StreamTransport]
|
||||
{.raises: [Defect, LPError].} =
|
||||
{.raises: [LPError].} =
|
||||
result = connect(api.address)
|
||||
|
||||
proc closeConnection*(api: DaemonAPI, transp: StreamTransport): Future[void] =
|
||||
@@ -512,7 +512,7 @@ proc socketExists(address: MultiAddress): Future[bool] {.async.} =
|
||||
var transp = await connect(address)
|
||||
await transp.closeWait()
|
||||
result = true
|
||||
except:
|
||||
except CatchableError:
|
||||
result = false
|
||||
|
||||
when defined(windows):
|
||||
@@ -522,7 +522,7 @@ when defined(windows):
|
||||
result = cast[int](getCurrentProcessId())
|
||||
else:
|
||||
proc getProcessId(): int =
|
||||
result = cast[int](posix.getpid())
|
||||
result = int(posix.getpid())
|
||||
|
||||
proc getSocket(pattern: string,
|
||||
count: ptr int): Future[MultiAddress] {.async.} =
|
||||
@@ -553,7 +553,7 @@ proc getSocket(pattern: string,
|
||||
closeSocket(sock)
|
||||
|
||||
# This is forward declaration needed for newDaemonApi()
|
||||
proc listPeers*(api: DaemonAPI): Future[seq[PeerInfo]] {.async, gcsafe.}
|
||||
proc listPeers*(api: DaemonAPI): Future[seq[PeerInfo]] {.async.}
|
||||
|
||||
proc copyEnv(): StringTableRef =
|
||||
## This procedure copy all environment variables into StringTable.
|
||||
@@ -755,13 +755,9 @@ proc newDaemonApi*(flags: set[P2PDaemonFlags] = {},
|
||||
|
||||
# Starting daemon process
|
||||
# echo "Starting ", cmd, " ", args.join(" ")
|
||||
api.process =
|
||||
try:
|
||||
api.process =
|
||||
exceptionToAssert:
|
||||
startProcess(cmd, "", args, env, {poParentStreams})
|
||||
except CatchableError as exc:
|
||||
raise exc
|
||||
except Exception as exc:
|
||||
raiseAssert exc.msg
|
||||
# Waiting until daemon will not be bound to control socket.
|
||||
while true:
|
||||
if not api.process.running():
|
||||
@@ -838,7 +834,7 @@ proc transactMessage(transp: StreamTransport,
|
||||
result = initProtoBuffer(message)
|
||||
|
||||
proc getPeerInfo(pb: ProtoBuffer): PeerInfo
|
||||
{.raises: [Defect, DaemonLocalError].} =
|
||||
{.raises: [DaemonLocalError].} =
|
||||
## Get PeerInfo object from ``pb``.
|
||||
result.addresses = newSeq[MultiAddress]()
|
||||
if pb.getRequiredField(1, result.peer).isErr():
|
||||
@@ -869,7 +865,7 @@ proc connect*(api: DaemonAPI, peer: PeerId,
|
||||
timeout))
|
||||
pb.withMessage() do:
|
||||
discard
|
||||
except:
|
||||
except CatchableError:
|
||||
await api.closeConnection(transp)
|
||||
|
||||
proc disconnect*(api: DaemonAPI, peer: PeerId) {.async.} =
|
||||
@@ -929,7 +925,7 @@ proc streamHandler(server: StreamServer, transp: StreamTransport) {.async.} =
|
||||
asyncSpawn handler(api, stream)
|
||||
|
||||
proc addHandler*(api: DaemonAPI, protocols: seq[string],
|
||||
handler: P2PStreamCallback) {.async, raises: [Defect, LPError].} =
|
||||
handler: P2PStreamCallback) {.async, raises: [LPError].} =
|
||||
## Add stream handler ``handler`` for set of protocols ``protocols``.
|
||||
var transp = await api.newConnection()
|
||||
let maddress = await getSocket(api.pattern, addr api.ucounter)
|
||||
@@ -999,7 +995,7 @@ proc cmTrimPeers*(api: DaemonAPI) {.async.} =
|
||||
await api.closeConnection(transp)
|
||||
|
||||
proc dhtGetSinglePeerInfo(pb: ProtoBuffer): PeerInfo
|
||||
{.raises: [Defect, DaemonLocalError].} =
|
||||
{.raises: [DaemonLocalError].} =
|
||||
var res: seq[byte]
|
||||
if pb.getRequiredField(2, res).isOk():
|
||||
result = initProtoBuffer(res).getPeerInfo()
|
||||
@@ -1007,42 +1003,42 @@ proc dhtGetSinglePeerInfo(pb: ProtoBuffer): PeerInfo
|
||||
raise newException(DaemonLocalError, "Missing required field `peer`!")
|
||||
|
||||
proc dhtGetSingleValue(pb: ProtoBuffer): seq[byte]
|
||||
{.raises: [Defect, DaemonLocalError].} =
|
||||
{.raises: [DaemonLocalError].} =
|
||||
result = newSeq[byte]()
|
||||
if pb.getRequiredField(3, result).isErr():
|
||||
raise newException(DaemonLocalError, "Missing field `value`!")
|
||||
|
||||
proc dhtGetSinglePublicKey(pb: ProtoBuffer): PublicKey
|
||||
{.raises: [Defect, DaemonLocalError].} =
|
||||
{.raises: [DaemonLocalError].} =
|
||||
if pb.getRequiredField(3, result).isErr():
|
||||
raise newException(DaemonLocalError, "Missing field `value`!")
|
||||
|
||||
proc dhtGetSinglePeerId(pb: ProtoBuffer): PeerId
|
||||
{.raises: [Defect, DaemonLocalError].} =
|
||||
{.raises: [DaemonLocalError].} =
|
||||
if pb.getRequiredField(3, result).isErr():
|
||||
raise newException(DaemonLocalError, "Missing field `value`!")
|
||||
|
||||
proc enterDhtMessage(pb: ProtoBuffer, rt: DHTResponseType): Protobuffer
|
||||
{.inline, raises: [Defect, DaemonLocalError].} =
|
||||
proc enterDhtMessage(pb: ProtoBuffer, rt: DHTResponseType): ProtoBuffer
|
||||
{.inline, raises: [DaemonLocalError].} =
|
||||
var dhtResponse: seq[byte]
|
||||
if pb.getRequiredField(ResponseType.DHT.int, dhtResponse).isOk():
|
||||
var pbDhtResponse = initProtoBuffer(dhtResponse)
|
||||
var dtype: uint
|
||||
if pbDhtResponse.getRequiredField(1, dtype).isErr():
|
||||
raise newException(DaemonLocalError, "Missing required DHT field `type`!")
|
||||
if dtype != cast[uint](rt):
|
||||
if dtype != safeConvert[uint](rt):
|
||||
raise newException(DaemonLocalError, "Wrong DHT answer type! ")
|
||||
|
||||
var value: seq[byte]
|
||||
if pbDhtResponse.getRequiredField(3, value).isErr():
|
||||
raise newException(DaemonLocalError, "Missing required DHT field `value`!")
|
||||
|
||||
|
||||
return initProtoBuffer(value)
|
||||
else:
|
||||
raise newException(DaemonLocalError, "Wrong message type!")
|
||||
|
||||
proc enterPsMessage(pb: ProtoBuffer): ProtoBuffer
|
||||
{.inline, raises: [Defect, DaemonLocalError].} =
|
||||
{.inline, raises: [DaemonLocalError].} =
|
||||
var res: seq[byte]
|
||||
if pb.getRequiredField(ResponseType.PUBSUB.int, res).isErr():
|
||||
raise newException(DaemonLocalError, "Wrong message type!")
|
||||
@@ -1050,13 +1046,13 @@ proc enterPsMessage(pb: ProtoBuffer): ProtoBuffer
|
||||
initProtoBuffer(res)
|
||||
|
||||
proc getDhtMessageType(pb: ProtoBuffer): DHTResponseType
|
||||
{.inline, raises: [Defect, DaemonLocalError].} =
|
||||
{.inline, raises: [DaemonLocalError].} =
|
||||
var dtype: uint
|
||||
if pb.getRequiredField(1, dtype).isErr():
|
||||
raise newException(DaemonLocalError, "Missing required DHT field `type`!")
|
||||
if dtype == cast[uint](DHTResponseType.VALUE):
|
||||
if dtype == safeConvert[uint](DHTResponseType.VALUE):
|
||||
result = DHTResponseType.VALUE
|
||||
elif dtype == cast[uint](DHTResponseType.END):
|
||||
elif dtype == safeConvert[uint](DHTResponseType.END):
|
||||
result = DHTResponseType.END
|
||||
else:
|
||||
raise newException(DaemonLocalError, "Wrong DHT answer type!")
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
## This module implements Pool of StreamTransport.
|
||||
import chronos
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## To enable dump of all incoming and outgoing unencrypted messages you need
|
||||
## to compile project with ``-d:libp2p_dump`` compile-time option. When this
|
||||
@@ -25,7 +25,7 @@
|
||||
## 5. LocalAddress: optional bytes
|
||||
## 6. RemoteAddress: optional bytes
|
||||
## 7. Message: required bytes
|
||||
import os, options
|
||||
import os
|
||||
import nimcrypto/utils, stew/endians2
|
||||
import protobuf/minprotobuf, stream/connection, protocols/secure/secure,
|
||||
multiaddress, peerid, varint, muxers/mplex/coder
|
||||
@@ -33,7 +33,7 @@ import protobuf/minprotobuf, stream/connection, protocols/secure/secure,
|
||||
from times import getTime, toUnix, fromUnix, nanosecond, format, Time,
|
||||
NanosecondRange, initTime
|
||||
from strutils import toHex, repeat
|
||||
export peerid, options, multiaddress
|
||||
export peerid, multiaddress
|
||||
|
||||
type
|
||||
FlowDirection* = enum
|
||||
@@ -43,10 +43,10 @@ type
|
||||
timestamp*: uint64
|
||||
direction*: FlowDirection
|
||||
message*: seq[byte]
|
||||
seqID*: Option[uint64]
|
||||
mtype*: Option[uint64]
|
||||
local*: Option[MultiAddress]
|
||||
remote*: Option[MultiAddress]
|
||||
seqID*: Opt[uint64]
|
||||
mtype*: Opt[uint64]
|
||||
local*: Opt[MultiAddress]
|
||||
remote*: Opt[MultiAddress]
|
||||
|
||||
const
|
||||
libp2p_dump_dir* {.strdefine.} = "nim-libp2p"
|
||||
@@ -72,7 +72,8 @@ proc dumpMessage*(conn: SecureConn, direction: FlowDirection,
|
||||
var pb = initProtoBuffer(options = {WithVarintLength})
|
||||
pb.write(2, getTimestamp())
|
||||
pb.write(4, uint64(direction))
|
||||
pb.write(6, conn.observedAddr)
|
||||
conn.observedAddr.withValue(oaddr):
|
||||
pb.write(6, oaddr)
|
||||
pb.write(7, data)
|
||||
pb.finish()
|
||||
|
||||
@@ -100,7 +101,7 @@ proc dumpMessage*(conn: SecureConn, direction: FlowDirection,
|
||||
finally:
|
||||
close(handle)
|
||||
|
||||
proc decodeDumpMessage*(data: openArray[byte]): Option[ProtoMessage] =
|
||||
proc decodeDumpMessage*(data: openArray[byte]): Opt[ProtoMessage] =
|
||||
## Decode protobuf's message ProtoMessage from array of bytes ``data``.
|
||||
var
|
||||
pb = initProtoBuffer(data)
|
||||
@@ -108,13 +109,12 @@ proc decodeDumpMessage*(data: openArray[byte]): Option[ProtoMessage] =
|
||||
ma1, ma2: MultiAddress
|
||||
pmsg: ProtoMessage
|
||||
|
||||
let res2 = pb.getField(2, pmsg.timestamp)
|
||||
if res2.isErr() or not(res2.get()):
|
||||
return none[ProtoMessage]()
|
||||
|
||||
let res4 = pb.getField(4, value)
|
||||
if res4.isErr() or not(res4.get()):
|
||||
return none[ProtoMessage]()
|
||||
let
|
||||
r2 = pb.getField(2, pmsg.timestamp)
|
||||
r4 = pb.getField(4, value)
|
||||
r7 = pb.getField(7, pmsg.message)
|
||||
if not r2.get(false) or not r4.get(false) or not r7.get(false):
|
||||
return Opt.none(ProtoMessage)
|
||||
|
||||
# `case` statement could not work here with an error "selector must be of an
|
||||
# ordinal type, float or string"
|
||||
@@ -124,30 +124,27 @@ proc decodeDumpMessage*(data: openArray[byte]): Option[ProtoMessage] =
|
||||
elif value == uint64(Incoming):
|
||||
Incoming
|
||||
else:
|
||||
return none[ProtoMessage]()
|
||||
return Opt.none(ProtoMessage)
|
||||
|
||||
let res7 = pb.getField(7, pmsg.message)
|
||||
if res7.isErr() or not(res7.get()):
|
||||
return none[ProtoMessage]()
|
||||
let r1 = pb.getField(1, value)
|
||||
if r1.get(false):
|
||||
pmsg.seqID = Opt.some(value)
|
||||
|
||||
value = 0'u64
|
||||
let res1 = pb.getField(1, value)
|
||||
if res1.isOk() and res1.get():
|
||||
pmsg.seqID = some(value)
|
||||
value = 0'u64
|
||||
let res3 = pb.getField(3, value)
|
||||
if res3.isOk() and res3.get():
|
||||
pmsg.mtype = some(value)
|
||||
let res5 = pb.getField(5, ma1)
|
||||
if res5.isOk() and res5.get():
|
||||
pmsg.local = some(ma1)
|
||||
let res6 = pb.getField(6, ma2)
|
||||
if res6.isOk() and res6.get():
|
||||
pmsg.remote = some(ma2)
|
||||
let r3 = pb.getField(3, value)
|
||||
if r3.get(false):
|
||||
pmsg.mtype = Opt.some(value)
|
||||
|
||||
some(pmsg)
|
||||
let
|
||||
r5 = pb.getField(5, ma1)
|
||||
r6 = pb.getField(6, ma2)
|
||||
if r5.get(false):
|
||||
pmsg.local = Opt.some(ma1)
|
||||
if r6.get(false):
|
||||
pmsg.remote = Opt.some(ma2)
|
||||
|
||||
iterator messages*(data: seq[byte]): Option[ProtoMessage] =
|
||||
Opt.some(pmsg)
|
||||
|
||||
iterator messages*(data: seq[byte]): Opt[ProtoMessage] =
|
||||
## Iterate over sequence of bytes and decode all the ``ProtoMessage``
|
||||
## messages we found.
|
||||
var value: uint64
|
||||
@@ -242,27 +239,19 @@ proc toString*(msg: ProtoMessage, dump = true): string =
|
||||
" >> "
|
||||
let address =
|
||||
block:
|
||||
let local =
|
||||
if msg.local.isSome():
|
||||
"[" & $(msg.local.get()) & "]"
|
||||
else:
|
||||
"[LOCAL]"
|
||||
let remote =
|
||||
if msg.remote.isSome():
|
||||
"[" & $(msg.remote.get()) & "]"
|
||||
else:
|
||||
"[REMOTE]"
|
||||
let local = block:
|
||||
msg.local.withValue(loc): "[" & $loc & "]"
|
||||
else: "[LOCAL]"
|
||||
let remote = block:
|
||||
msg.remote.withValue(rem): "[" & $rem & "]"
|
||||
else: "[REMOTE]"
|
||||
local & direction & remote
|
||||
let seqid =
|
||||
if msg.seqID.isSome():
|
||||
"seqID = " & $(msg.seqID.get()) & " "
|
||||
else:
|
||||
""
|
||||
let mtype =
|
||||
if msg.mtype.isSome():
|
||||
"type = " & $(msg.mtype.get()) & " "
|
||||
else:
|
||||
""
|
||||
let seqid = block:
|
||||
msg.seqID.withValue(seqid): "seqID = " & $seqid & " "
|
||||
else: ""
|
||||
let mtype = block:
|
||||
msg.mtype.withValue(typ): "type = " & $typ & " "
|
||||
else: ""
|
||||
res.add(" ")
|
||||
res.add(address)
|
||||
res.add(" ")
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2021 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import chronos
|
||||
import stew/results
|
||||
import peerid,
|
||||
stream/connection,
|
||||
transports/transport
|
||||
|
||||
export results
|
||||
|
||||
type
|
||||
Dial* = ref object of RootObj
|
||||
|
||||
@@ -21,13 +24,23 @@ method connect*(
|
||||
self: Dial,
|
||||
peerId: PeerId,
|
||||
addrs: seq[MultiAddress],
|
||||
forceDial = false) {.async, base.} =
|
||||
forceDial = false,
|
||||
reuseConnection = true,
|
||||
dir = Direction.Out) {.async, base.} =
|
||||
## connect remote peer without negotiating
|
||||
## a protocol
|
||||
##
|
||||
|
||||
doAssert(false, "Not implemented!")
|
||||
|
||||
method connect*(
|
||||
self: Dial,
|
||||
address: MultiAddress,
|
||||
allowUnknownPeerId = false): Future[PeerId] {.async, base.} =
|
||||
## Connects to a peer and retrieve its PeerId
|
||||
|
||||
doAssert(false, "Not implemented!")
|
||||
|
||||
method dial*(
|
||||
self: Dial,
|
||||
peerId: PeerId,
|
||||
@@ -55,3 +68,9 @@ method addTransport*(
|
||||
self: Dial,
|
||||
transport: Transport) {.base.} =
|
||||
doAssert(false, "Not implemented!")
|
||||
|
||||
method tryDial*(
|
||||
self: Dial,
|
||||
peerId: PeerId,
|
||||
addrs: seq[MultiAddress]): Future[Opt[MultiAddress]] {.async, base.} =
|
||||
doAssert(false, "Not implemented!")
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2021 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
import std/[sugar, tables]
|
||||
import std/tables
|
||||
|
||||
import stew/results
|
||||
import pkg/[chronos,
|
||||
chronicles,
|
||||
metrics]
|
||||
@@ -16,14 +17,18 @@ import pkg/[chronos,
|
||||
import dial,
|
||||
peerid,
|
||||
peerinfo,
|
||||
peerstore,
|
||||
multicodec,
|
||||
muxers/muxer,
|
||||
multistream,
|
||||
connmanager,
|
||||
stream/connection,
|
||||
transports/transport,
|
||||
nameresolving/nameresolver,
|
||||
upgrademngrs/upgrade,
|
||||
errors
|
||||
|
||||
export dial, errors
|
||||
export dial, errors, results
|
||||
|
||||
logScope:
|
||||
topics = "libp2p dialer"
|
||||
@@ -31,130 +36,175 @@ logScope:
|
||||
declareCounter(libp2p_total_dial_attempts, "total attempted dials")
|
||||
declareCounter(libp2p_successful_dials, "dialed successful peers")
|
||||
declareCounter(libp2p_failed_dials, "failed dials")
|
||||
declareCounter(libp2p_failed_upgrades_outgoing, "outgoing connections failed upgrades")
|
||||
|
||||
type
|
||||
DialFailedError* = object of LPError
|
||||
|
||||
Dialer* = ref object of Dial
|
||||
localPeerId*: PeerId
|
||||
ms: MultistreamSelect
|
||||
connManager: ConnManager
|
||||
dialLock: Table[PeerId, AsyncLock]
|
||||
transports: seq[Transport]
|
||||
peerStore: PeerStore
|
||||
nameResolver: NameResolver
|
||||
|
||||
proc dialAndUpgrade(
|
||||
self: Dialer,
|
||||
peerId: PeerId,
|
||||
peerId: Opt[PeerId],
|
||||
hostname: string,
|
||||
address: MultiAddress,
|
||||
dir = Direction.Out):
|
||||
Future[Muxer] {.async.} =
|
||||
|
||||
for transport in self.transports: # for each transport
|
||||
if transport.handles(address): # check if it can dial it
|
||||
trace "Dialing address", address, peerId = peerId.get(default(PeerId)), hostname
|
||||
let dialed =
|
||||
try:
|
||||
libp2p_total_dial_attempts.inc()
|
||||
await transport.dial(hostname, address, peerId)
|
||||
except CancelledError as exc:
|
||||
debug "Dialing canceled", err = exc.msg, peerId = peerId.get(default(PeerId))
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
debug "Dialing failed", err = exc.msg, peerId = peerId.get(default(PeerId))
|
||||
libp2p_failed_dials.inc()
|
||||
return nil # Try the next address
|
||||
|
||||
libp2p_successful_dials.inc()
|
||||
|
||||
let mux =
|
||||
try:
|
||||
# This is for the very specific case of a simultaneous dial during DCUtR. In this case, both sides will have
|
||||
# an Outbound direction at the transport level. Therefore we update the DCUtR initiator transport direction to Inbound.
|
||||
# The if below is more general and might handle other use cases in the future.
|
||||
if dialed.dir != dir:
|
||||
dialed.dir = dir
|
||||
await transport.upgrade(dialed, peerId)
|
||||
except CancelledError as exc:
|
||||
await dialed.close()
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
# If we failed to establish the connection through one transport,
|
||||
# we won't succeeded through another - no use in trying again
|
||||
await dialed.close()
|
||||
debug "Connection upgrade failed", err = exc.msg, peerId = peerId.get(default(PeerId))
|
||||
if dialed.dir == Direction.Out:
|
||||
libp2p_failed_upgrades_outgoing.inc()
|
||||
else:
|
||||
libp2p_failed_upgrades_incoming.inc()
|
||||
|
||||
# Try other address
|
||||
return nil
|
||||
|
||||
doAssert not isNil(mux), "connection died after upgrade " & $dialed.dir
|
||||
debug "Dial successful", peerId = mux.connection.peerId
|
||||
return mux
|
||||
return nil
|
||||
|
||||
proc expandDnsAddr(
|
||||
self: Dialer,
|
||||
peerId: Opt[PeerId],
|
||||
address: MultiAddress): Future[seq[(MultiAddress, Opt[PeerId])]] {.async.} =
|
||||
|
||||
if not DNSADDR.matchPartial(address): return @[(address, peerId)]
|
||||
if isNil(self.nameResolver):
|
||||
info "Can't resolve DNSADDR without NameResolver", ma=address
|
||||
return @[]
|
||||
|
||||
let
|
||||
toResolve =
|
||||
if peerId.isSome:
|
||||
address & MultiAddress.init(multiCodec("p2p"), peerId.tryGet()).tryGet()
|
||||
else:
|
||||
address
|
||||
resolved = await self.nameResolver.resolveDnsAddr(toResolve)
|
||||
|
||||
for resolvedAddress in resolved:
|
||||
let lastPart = resolvedAddress[^1].tryGet()
|
||||
if lastPart.protoCode == Result[MultiCodec, string].ok(multiCodec("p2p")):
|
||||
let
|
||||
peerIdBytes = lastPart.protoArgument().tryGet()
|
||||
addrPeerId = PeerId.init(peerIdBytes).tryGet()
|
||||
result.add((resolvedAddress[0..^2].tryGet(), Opt.some(addrPeerId)))
|
||||
else:
|
||||
result.add((resolvedAddress, peerId))
|
||||
|
||||
proc dialAndUpgrade(
|
||||
self: Dialer,
|
||||
peerId: Opt[PeerId],
|
||||
addrs: seq[MultiAddress],
|
||||
forceDial: bool):
|
||||
Future[Connection] {.async.} =
|
||||
debug "Dialing peer", peerId
|
||||
dir = Direction.Out):
|
||||
Future[Muxer] {.async.} =
|
||||
|
||||
for address in addrs: # for each address
|
||||
let
|
||||
hostname = address.getHostname()
|
||||
resolvedAddresses =
|
||||
if isNil(self.nameResolver): @[address]
|
||||
else: await self.nameResolver.resolveMAddress(address)
|
||||
debug "Dialing peer", peerId = peerId.get(default(PeerId))
|
||||
|
||||
for a in resolvedAddresses: # for each resolved address
|
||||
for transport in self.transports: # for each transport
|
||||
if transport.handles(a): # check if it can dial it
|
||||
trace "Dialing address", address = $a, peerId, hostname
|
||||
let dialed = try:
|
||||
libp2p_total_dial_attempts.inc()
|
||||
# await a connection slot when the total
|
||||
# connection count is equal to `maxConns`
|
||||
#
|
||||
# Need to copy to avoid "cannot be captured" errors in Nim-1.4.x.
|
||||
let
|
||||
transportCopy = transport
|
||||
addressCopy = a
|
||||
await self.connManager.trackOutgoingConn(
|
||||
() => transportCopy.dial(hostname, addressCopy),
|
||||
forceDial
|
||||
)
|
||||
except TooManyConnectionsError as exc:
|
||||
trace "Connection limit reached!"
|
||||
raise exc
|
||||
except CancelledError as exc:
|
||||
debug "Dialing canceled", msg = exc.msg, peerId
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
debug "Dialing failed", msg = exc.msg, peerId
|
||||
libp2p_failed_dials.inc()
|
||||
continue # Try the next address
|
||||
for rawAddress in addrs:
|
||||
# resolve potential dnsaddr
|
||||
let addresses = await self.expandDnsAddr(peerId, rawAddress)
|
||||
|
||||
# make sure to assign the peer to the connection
|
||||
dialed.peerId = peerId
|
||||
for (expandedAddress, addrPeerId) in addresses:
|
||||
# DNS resolution
|
||||
let
|
||||
hostname = expandedAddress.getHostname()
|
||||
resolvedAddresses =
|
||||
if isNil(self.nameResolver): @[expandedAddress]
|
||||
else: await self.nameResolver.resolveMAddress(expandedAddress)
|
||||
|
||||
# also keep track of the connection's bottom unsafe transport direction
|
||||
# required by gossipsub scoring
|
||||
dialed.transportDir = Direction.Out
|
||||
for resolvedAddress in resolvedAddresses:
|
||||
result = await self.dialAndUpgrade(addrPeerId, hostname, resolvedAddress, dir)
|
||||
if not isNil(result):
|
||||
return result
|
||||
|
||||
libp2p_successful_dials.inc()
|
||||
proc tryReusingConnection(self: Dialer, peerId: PeerId): Opt[Muxer] =
|
||||
let muxer = self.connManager.selectMuxer(peerId)
|
||||
if muxer == nil:
|
||||
return Opt.none(Muxer)
|
||||
|
||||
let conn = try:
|
||||
await transport.upgradeOutgoing(dialed)
|
||||
except CatchableError as exc:
|
||||
# If we failed to establish the connection through one transport,
|
||||
# we won't succeeded through another - no use in trying again
|
||||
await dialed.close()
|
||||
debug "Upgrade failed", msg = exc.msg, peerId
|
||||
if exc isnot CancelledError:
|
||||
libp2p_failed_upgrades_outgoing.inc()
|
||||
raise exc
|
||||
|
||||
doAssert not isNil(conn), "connection died after upgradeOutgoing"
|
||||
debug "Dial successful", conn, peerId = conn.peerId
|
||||
return conn
|
||||
trace "Reusing existing connection", muxer, direction = $muxer.connection.dir
|
||||
return Opt.some(muxer)
|
||||
|
||||
proc internalConnect(
|
||||
self: Dialer,
|
||||
peerId: PeerId,
|
||||
peerId: Opt[PeerId],
|
||||
addrs: seq[MultiAddress],
|
||||
forceDial: bool):
|
||||
Future[Connection] {.async.} =
|
||||
if self.localPeerId == peerId:
|
||||
forceDial: bool,
|
||||
reuseConnection = true,
|
||||
dir = Direction.Out):
|
||||
Future[Muxer] {.async.} =
|
||||
if Opt.some(self.localPeerId) == peerId:
|
||||
raise newException(CatchableError, "can't dial self!")
|
||||
|
||||
# Ensure there's only one in-flight attempt per peer
|
||||
let lock = self.dialLock.mgetOrPut(peerId, newAsyncLock())
|
||||
let lock = self.dialLock.mgetOrPut(peerId.get(default(PeerId)), newAsyncLock())
|
||||
try:
|
||||
await lock.acquire()
|
||||
|
||||
# Check if we have a connection already and try to reuse it
|
||||
var conn = self.connManager.selectConn(peerId)
|
||||
if conn != nil:
|
||||
if conn.atEof or conn.closed:
|
||||
# This connection should already have been removed from the connection
|
||||
# manager - it's essentially a bug that we end up here - we'll fail
|
||||
# for now, hoping that this will clean themselves up later...
|
||||
warn "dead connection in connection manager", conn
|
||||
await conn.close()
|
||||
raise newException(DialFailedError, "Zombie connection encountered")
|
||||
if reuseConnection:
|
||||
peerId.withValue(peerId):
|
||||
self.tryReusingConnection(peerId).withValue(mux):
|
||||
return mux
|
||||
|
||||
trace "Reusing existing connection", conn, direction = $conn.dir
|
||||
return conn
|
||||
|
||||
conn = await self.dialAndUpgrade(peerId, addrs, forceDial)
|
||||
if isNil(conn): # None of the addresses connected
|
||||
let slot = self.connManager.getOutgoingSlot(forceDial)
|
||||
let muxed =
|
||||
try:
|
||||
await self.dialAndUpgrade(peerId, addrs, dir)
|
||||
except CatchableError as exc:
|
||||
slot.release()
|
||||
raise exc
|
||||
slot.trackMuxer(muxed)
|
||||
if isNil(muxed): # None of the addresses connected
|
||||
raise newException(DialFailedError, "Unable to establish outgoing link")
|
||||
|
||||
# We already check for this in Connection manager
|
||||
# but a disconnect could have happened right after
|
||||
# we've added the connection so we check again
|
||||
# to prevent races due to that.
|
||||
if conn.closed() or conn.atEof():
|
||||
# This can happen when the other ends drops us
|
||||
# before we get a chance to return the connection
|
||||
# back to the dialer.
|
||||
trace "Connection dead on arrival", conn
|
||||
raise newLPStreamClosedError()
|
||||
try:
|
||||
self.connManager.storeMuxer(muxed)
|
||||
await self.peerStore.identify(muxed)
|
||||
except CatchableError as exc:
|
||||
trace "Failed to finish outgoung upgrade", err=exc.msg
|
||||
await muxed.close()
|
||||
raise exc
|
||||
|
||||
return conn
|
||||
return muxed
|
||||
finally:
|
||||
if lock.locked():
|
||||
lock.release()
|
||||
@@ -163,28 +213,71 @@ method connect*(
|
||||
self: Dialer,
|
||||
peerId: PeerId,
|
||||
addrs: seq[MultiAddress],
|
||||
forceDial = false) {.async.} =
|
||||
forceDial = false,
|
||||
reuseConnection = true,
|
||||
dir = Direction.Out) {.async.} =
|
||||
## connect remote peer without negotiating
|
||||
## a protocol
|
||||
##
|
||||
|
||||
if self.connManager.connCount(peerId) > 0:
|
||||
if self.connManager.connCount(peerId) > 0 and reuseConnection:
|
||||
return
|
||||
|
||||
discard await self.internalConnect(peerId, addrs, forceDial)
|
||||
discard await self.internalConnect(Opt.some(peerId), addrs, forceDial, reuseConnection, dir)
|
||||
|
||||
method connect*(
|
||||
self: Dialer,
|
||||
address: MultiAddress,
|
||||
allowUnknownPeerId = false): Future[PeerId] {.async.} =
|
||||
## Connects to a peer and retrieve its PeerId
|
||||
|
||||
parseFullAddress(address).toOpt().withValue(fullAddress):
|
||||
return (await self.internalConnect(
|
||||
Opt.some(fullAddress[0]),
|
||||
@[fullAddress[1]],
|
||||
false)).connection.peerId
|
||||
|
||||
if allowUnknownPeerId == false:
|
||||
raise newException(DialFailedError, "Address without PeerID and unknown peer id disabled!")
|
||||
|
||||
return (await self.internalConnect(
|
||||
Opt.none(PeerId),
|
||||
@[address],
|
||||
false)).connection.peerId
|
||||
|
||||
proc negotiateStream(
|
||||
self: Dialer,
|
||||
conn: Connection,
|
||||
protos: seq[string]): Future[Connection] {.async.} =
|
||||
trace "Negotiating stream", conn, protos
|
||||
let selected = await self.ms.select(conn, protos)
|
||||
let selected = await MultistreamSelect.select(conn, protos)
|
||||
if not protos.contains(selected):
|
||||
await conn.closeWithEOF()
|
||||
raise newException(DialFailedError, "Unable to select sub-protocol " & $protos)
|
||||
|
||||
return conn
|
||||
|
||||
method tryDial*(
|
||||
self: Dialer,
|
||||
peerId: PeerId,
|
||||
addrs: seq[MultiAddress]): Future[Opt[MultiAddress]] {.async.} =
|
||||
## Create a protocol stream in order to check
|
||||
## if a connection is possible.
|
||||
## Doesn't use the Connection Manager to save it.
|
||||
##
|
||||
|
||||
trace "Check if it can dial", peerId, addrs
|
||||
try:
|
||||
let mux = await self.dialAndUpgrade(Opt.some(peerId), addrs)
|
||||
if mux.isNil():
|
||||
raise newException(DialFailedError, "No valid multiaddress")
|
||||
await mux.close()
|
||||
return mux.connection.observedAddr
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
raise newException(DialFailedError, exc.msg)
|
||||
|
||||
method dial*(
|
||||
self: Dialer,
|
||||
peerId: PeerId,
|
||||
@@ -211,7 +304,7 @@ method dial*(
|
||||
##
|
||||
|
||||
var
|
||||
conn: Connection
|
||||
conn: Muxer
|
||||
stream: Connection
|
||||
|
||||
proc cleanup() {.async.} =
|
||||
@@ -223,7 +316,7 @@ method dial*(
|
||||
|
||||
try:
|
||||
trace "Dialing (new)", peerId, protos
|
||||
conn = await self.internalConnect(peerId, addrs, forceDial)
|
||||
conn = await self.internalConnect(Opt.some(peerId), addrs, forceDial)
|
||||
trace "Opening stream", conn
|
||||
stream = await self.connManager.getStream(conn)
|
||||
|
||||
@@ -237,7 +330,7 @@ method dial*(
|
||||
await cleanup()
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
debug "Error dialing", conn, msg = exc.msg
|
||||
debug "Error dialing", conn, err = exc.msg
|
||||
await cleanup()
|
||||
raise exc
|
||||
|
||||
@@ -248,12 +341,12 @@ proc new*(
|
||||
T: type Dialer,
|
||||
localPeerId: PeerId,
|
||||
connManager: ConnManager,
|
||||
peerStore: PeerStore,
|
||||
transports: seq[Transport],
|
||||
ms: MultistreamSelect,
|
||||
nameResolver: NameResolver = nil): Dialer =
|
||||
|
||||
T(localPeerId: localPeerId,
|
||||
connManager: connManager,
|
||||
transports: transports,
|
||||
ms: ms,
|
||||
peerStore: peerStore,
|
||||
nameResolver: nameResolver)
|
||||
|
||||
174
libp2p/discovery/discoverymngr.nim
Normal file
174
libp2p/discovery/discoverymngr.nim
Normal file
@@ -0,0 +1,174 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/sequtils
|
||||
import chronos, chronicles, stew/results
|
||||
import ../errors
|
||||
|
||||
type
|
||||
BaseAttr = ref object of RootObj
|
||||
comparator: proc(f, c: BaseAttr): bool {.gcsafe, raises: [].}
|
||||
|
||||
Attribute[T] = ref object of BaseAttr
|
||||
value: T
|
||||
|
||||
PeerAttributes* = object
|
||||
attributes: seq[BaseAttr]
|
||||
|
||||
DiscoveryService* = distinct string
|
||||
|
||||
proc `==`*(a, b: DiscoveryService): bool {.borrow.}
|
||||
|
||||
proc ofType*[T](f: BaseAttr, _: type[T]): bool =
|
||||
return f of Attribute[T]
|
||||
|
||||
proc to*[T](f: BaseAttr, _: type[T]): T =
|
||||
Attribute[T](f).value
|
||||
|
||||
proc add*[T](pa: var PeerAttributes,
|
||||
value: T) =
|
||||
pa.attributes.add(Attribute[T](
|
||||
value: value,
|
||||
comparator: proc(f: BaseAttr, c: BaseAttr): bool =
|
||||
f.ofType(T) and c.ofType(T) and f.to(T) == c.to(T)
|
||||
)
|
||||
)
|
||||
|
||||
iterator items*(pa: PeerAttributes): BaseAttr =
|
||||
for f in pa.attributes:
|
||||
yield f
|
||||
|
||||
proc getAll*[T](pa: PeerAttributes, t: typedesc[T]): seq[T] =
|
||||
for f in pa.attributes:
|
||||
if f.ofType(T):
|
||||
result.add(f.to(T))
|
||||
|
||||
proc `{}`*[T](pa: PeerAttributes, t: typedesc[T]): Opt[T] =
|
||||
for f in pa.attributes:
|
||||
if f.ofType(T):
|
||||
return Opt.some(f.to(T))
|
||||
Opt.none(T)
|
||||
|
||||
proc `[]`*[T](pa: PeerAttributes, t: typedesc[T]): T {.raises: [KeyError].} =
|
||||
pa{T}.valueOr: raise newException(KeyError, "Attritute not found")
|
||||
|
||||
proc match*(pa, candidate: PeerAttributes): bool =
|
||||
for f in pa.attributes:
|
||||
block oneAttribute:
|
||||
for field in candidate.attributes:
|
||||
if field.comparator(field, f):
|
||||
break oneAttribute
|
||||
return false
|
||||
return true
|
||||
|
||||
type
|
||||
PeerFoundCallback* = proc(pa: PeerAttributes) {.raises: [], gcsafe.}
|
||||
|
||||
DiscoveryInterface* = ref object of RootObj
|
||||
onPeerFound*: PeerFoundCallback
|
||||
toAdvertise*: PeerAttributes
|
||||
advertisementUpdated*: AsyncEvent
|
||||
advertiseLoop*: Future[void]
|
||||
|
||||
method request*(self: DiscoveryInterface, pa: PeerAttributes) {.async, base.} =
|
||||
doAssert(false, "Not implemented!")
|
||||
|
||||
method advertise*(self: DiscoveryInterface) {.async, base.} =
|
||||
doAssert(false, "Not implemented!")
|
||||
|
||||
type
|
||||
DiscoveryError* = object of LPError
|
||||
DiscoveryFinished* = object of LPError
|
||||
|
||||
DiscoveryQuery* = ref object
|
||||
attr: PeerAttributes
|
||||
peers: AsyncQueue[PeerAttributes]
|
||||
finished: bool
|
||||
futs: seq[Future[void]]
|
||||
|
||||
DiscoveryManager* = ref object
|
||||
interfaces: seq[DiscoveryInterface]
|
||||
queries: seq[DiscoveryQuery]
|
||||
|
||||
proc add*(dm: DiscoveryManager, di: DiscoveryInterface) =
|
||||
dm.interfaces &= di
|
||||
|
||||
di.onPeerFound = proc (pa: PeerAttributes) =
|
||||
for query in dm.queries:
|
||||
if query.attr.match(pa):
|
||||
try:
|
||||
query.peers.putNoWait(pa)
|
||||
except AsyncQueueFullError as exc:
|
||||
debug "Cannot push discovered peer to queue"
|
||||
|
||||
proc request*(dm: DiscoveryManager, pa: PeerAttributes): DiscoveryQuery =
|
||||
var query = DiscoveryQuery(attr: pa, peers: newAsyncQueue[PeerAttributes]())
|
||||
for i in dm.interfaces:
|
||||
query.futs.add(i.request(pa))
|
||||
dm.queries.add(query)
|
||||
dm.queries.keepItIf(it.futs.anyIt(not it.finished()))
|
||||
return query
|
||||
|
||||
proc request*[T](dm: DiscoveryManager, value: T): DiscoveryQuery =
|
||||
var pa: PeerAttributes
|
||||
pa.add(value)
|
||||
return dm.request(pa)
|
||||
|
||||
proc advertise*[T](dm: DiscoveryManager, value: T) =
|
||||
for i in dm.interfaces:
|
||||
i.toAdvertise.add(value)
|
||||
if i.advertiseLoop.isNil:
|
||||
i.advertisementUpdated = newAsyncEvent()
|
||||
i.advertiseLoop = i.advertise()
|
||||
else:
|
||||
i.advertisementUpdated.fire()
|
||||
|
||||
template forEach*(query: DiscoveryQuery, code: untyped) =
|
||||
## Will execute `code` for each discovered peer. The
|
||||
## peer attritubtes are available through the variable
|
||||
## `peer`
|
||||
|
||||
proc forEachInternal(q: DiscoveryQuery) {.async.} =
|
||||
while true:
|
||||
let peer {.inject.} =
|
||||
try: await q.getPeer()
|
||||
except DiscoveryFinished: return
|
||||
code
|
||||
|
||||
asyncSpawn forEachInternal(query)
|
||||
|
||||
proc stop*(query: DiscoveryQuery) =
|
||||
query.finished = true
|
||||
for r in query.futs:
|
||||
if not r.finished(): r.cancel()
|
||||
|
||||
proc stop*(dm: DiscoveryManager) =
|
||||
for q in dm.queries:
|
||||
q.stop()
|
||||
for i in dm.interfaces:
|
||||
if isNil(i.advertiseLoop): continue
|
||||
i.advertiseLoop.cancel()
|
||||
|
||||
proc getPeer*(query: DiscoveryQuery): Future[PeerAttributes] {.async.} =
|
||||
let getter = query.peers.popFirst()
|
||||
|
||||
try:
|
||||
await getter or allFinished(query.futs)
|
||||
except CancelledError as exc:
|
||||
getter.cancel()
|
||||
raise exc
|
||||
|
||||
if not finished(getter):
|
||||
if query.finished:
|
||||
raise newException(DiscoveryFinished, "Discovery query stopped")
|
||||
# discovery loops only finish when they don't handle the query
|
||||
raise newException(DiscoveryError, "Unable to find any peer matching this request")
|
||||
return await getter
|
||||
78
libp2p/discovery/rendezvousinterface.nim
Normal file
78
libp2p/discovery/rendezvousinterface.nim
Normal file
@@ -0,0 +1,78 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import chronos
|
||||
import ./discoverymngr,
|
||||
../protocols/rendezvous,
|
||||
../peerid
|
||||
|
||||
type
|
||||
RendezVousInterface* = ref object of DiscoveryInterface
|
||||
rdv*: RendezVous
|
||||
timeToRequest: Duration
|
||||
timeToAdvertise: Duration
|
||||
ttl: Duration
|
||||
|
||||
RdvNamespace* = distinct string
|
||||
|
||||
proc `==`*(a, b: RdvNamespace): bool {.borrow.}
|
||||
|
||||
method request*(self: RendezVousInterface, pa: PeerAttributes) {.async.} =
|
||||
var namespace = ""
|
||||
for attr in pa:
|
||||
if attr.ofType(RdvNamespace):
|
||||
namespace = string attr.to(RdvNamespace)
|
||||
elif attr.ofType(DiscoveryService):
|
||||
namespace = string attr.to(DiscoveryService)
|
||||
elif attr.ofType(PeerId):
|
||||
namespace = $attr.to(PeerId)
|
||||
else:
|
||||
# unhandled type
|
||||
return
|
||||
while true:
|
||||
for pr in await self.rdv.request(namespace):
|
||||
var peer: PeerAttributes
|
||||
peer.add(pr.peerId)
|
||||
for address in pr.addresses:
|
||||
peer.add(address.address)
|
||||
|
||||
peer.add(DiscoveryService(namespace))
|
||||
peer.add(RdvNamespace(namespace))
|
||||
self.onPeerFound(peer)
|
||||
|
||||
await sleepAsync(self.timeToRequest)
|
||||
|
||||
method advertise*(self: RendezVousInterface) {.async.} =
|
||||
while true:
|
||||
var toAdvertise: seq[string]
|
||||
for attr in self.toAdvertise:
|
||||
if attr.ofType(RdvNamespace):
|
||||
toAdvertise.add string attr.to(RdvNamespace)
|
||||
elif attr.ofType(DiscoveryService):
|
||||
toAdvertise.add string attr.to(DiscoveryService)
|
||||
elif attr.ofType(PeerId):
|
||||
toAdvertise.add $attr.to(PeerId)
|
||||
|
||||
self.advertisementUpdated.clear()
|
||||
for toAdv in toAdvertise:
|
||||
try:
|
||||
await self.rdv.advertise(toAdv, self.ttl)
|
||||
except CatchableError as error:
|
||||
debug "RendezVous advertise error: ", msg = error.msg
|
||||
|
||||
await sleepAsync(self.timeToAdvertise) or self.advertisementUpdated.wait()
|
||||
|
||||
proc new*(T: typedesc[RendezVousInterface],
|
||||
rdv: RendezVous,
|
||||
ttr: Duration = 1.minutes,
|
||||
tta: Duration = 1.minutes,
|
||||
ttl: Duration = MinimumDuration): RendezVousInterface =
|
||||
T(rdv: rdv, timeToRequest: ttr, timeToAdvertise: tta, ttl: ttl)
|
||||
@@ -19,7 +19,8 @@ func toException*(e: string): ref LPError =
|
||||
# sadly nim needs more love for hygienic templates
|
||||
# so here goes the macro, its based on the proc/template version
|
||||
# and uses quote do so it's quite readable
|
||||
macro checkFutures*[T](futs: seq[Future[T]], exclude: untyped = []): untyped =
|
||||
# TODO https://github.com/nim-lang/Nim/issues/22936
|
||||
macro checkFutures*[F](futs: seq[F], exclude: untyped = []): untyped =
|
||||
let nexclude = exclude.len
|
||||
case nexclude
|
||||
of 0:
|
||||
@@ -43,34 +44,3 @@ macro checkFutures*[T](futs: seq[Future[T]], exclude: untyped = []): untyped =
|
||||
# We still don't abort but warn
|
||||
debug "A future has failed, enable trace logging for details", error=exc.name
|
||||
trace "Exception details", msg=exc.msg
|
||||
|
||||
proc allFuturesThrowing*[T](args: varargs[Future[T]]): Future[void] =
|
||||
var futs: seq[Future[T]]
|
||||
for fut in args:
|
||||
futs &= fut
|
||||
proc call() {.async.} =
|
||||
var first: ref CatchableError = nil
|
||||
futs = await allFinished(futs)
|
||||
for fut in futs:
|
||||
if fut.failed:
|
||||
let err = fut.readError()
|
||||
if err of Defect:
|
||||
raise err
|
||||
else:
|
||||
if err of CancelledError:
|
||||
raise err
|
||||
if isNil(first):
|
||||
first = err
|
||||
if not isNil(first):
|
||||
raise first
|
||||
|
||||
return call()
|
||||
|
||||
template tryAndWarn*(message: static[string]; body: untyped): untyped =
|
||||
try:
|
||||
body
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
debug "An exception has ocurred, enable trace logging for details", name = exc.name, msg = message
|
||||
trace "Exception details", exc = exc.msg
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements MultiAddress.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
{.push public.}
|
||||
|
||||
import pkg/chronos
|
||||
import std/[nativesockets, hashes]
|
||||
import tables, strutils, sets, stew/shims/net
|
||||
import pkg/chronos, chronicles
|
||||
import std/[nativesockets, net, hashes]
|
||||
import tables, strutils, sets
|
||||
import multicodec, multihash, multibase, transcoder, vbuffer, peerid,
|
||||
protobuf/minprotobuf, errors
|
||||
protobuf/minprotobuf, errors, utility
|
||||
import stew/[base58, base32, endians2, results]
|
||||
export results, minprotobuf, vbuffer
|
||||
export results, minprotobuf, vbuffer, utility
|
||||
|
||||
logScope:
|
||||
topics = "libp2p multiaddress"
|
||||
|
||||
type
|
||||
MAKind* = enum
|
||||
@@ -30,7 +34,7 @@ type
|
||||
coder*: Transcoder
|
||||
|
||||
MultiAddress* = object
|
||||
data*: VBuffer
|
||||
data: VBuffer
|
||||
|
||||
MaPatternOp* = enum
|
||||
Eq, Or, And
|
||||
@@ -59,6 +63,10 @@ const
|
||||
IPPROTO_TCP = Protocol.IPPROTO_TCP
|
||||
IPPROTO_UDP = Protocol.IPPROTO_UDP
|
||||
|
||||
proc data*(ma: MultiAddress): VBuffer =
|
||||
## Returns the data buffer of the MultiAddress.
|
||||
return ma.data
|
||||
|
||||
proc hash*(a: MultiAddress): Hash =
|
||||
var h: Hash = 0
|
||||
h = h !& hash(a.data.buffer)
|
||||
@@ -72,7 +80,7 @@ proc ip4StB(s: string, vb: var VBuffer): bool =
|
||||
if a.family == IpAddressFamily.IPv4:
|
||||
vb.writeArray(a.address_v4)
|
||||
result = true
|
||||
except:
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
proc ip4BtS(vb: var VBuffer, s: var string): bool =
|
||||
@@ -95,7 +103,7 @@ proc ip6StB(s: string, vb: var VBuffer): bool =
|
||||
if a.family == IpAddressFamily.IPv6:
|
||||
vb.writeArray(a.address_v6)
|
||||
result = true
|
||||
except:
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
proc ip6BtS(vb: var VBuffer, s: var string): bool =
|
||||
@@ -111,23 +119,46 @@ proc ip6VB(vb: var VBuffer): bool =
|
||||
if vb.readArray(a.address_v6) == 16:
|
||||
result = true
|
||||
|
||||
proc ip6zoneStB(s: string, vb: var VBuffer): bool =
|
||||
## IPv6 stringToBuffer() implementation.
|
||||
template pathStringToBuffer(s: string, vb: var VBuffer): bool =
|
||||
if len(s) > 0:
|
||||
vb.writeSeq(s)
|
||||
result = true
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
template pathBufferToString(vb: var VBuffer, s: var string): bool =
|
||||
s = ""
|
||||
if (vb.readSeq(s) > 0) and (len(s) > 0):
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
template pathBufferToStringNoSlash(vb: var VBuffer, s: var string): bool =
|
||||
s = ""
|
||||
if (vb.readSeq(s) > 0) and (len(s) > 0) and (s.find('/') == -1):
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
template pathValidateBuffer(vb: var VBuffer): bool =
|
||||
var s = ""
|
||||
pathBufferToString(vb, s)
|
||||
|
||||
template pathValidateBufferNoSlash(vb: var VBuffer): bool =
|
||||
var s = ""
|
||||
pathBufferToStringNoSlash(vb, s)
|
||||
|
||||
proc ip6zoneStB(s: string, vb: var VBuffer): bool =
|
||||
## IPv6 stringToBuffer() implementation.
|
||||
pathStringToBuffer(s, vb)
|
||||
|
||||
proc ip6zoneBtS(vb: var VBuffer, s: var string): bool =
|
||||
## IPv6 bufferToString() implementation.
|
||||
if vb.readSeq(s) > 0:
|
||||
result = true
|
||||
pathBufferToStringNoSlash(vb, s)
|
||||
|
||||
proc ip6zoneVB(vb: var VBuffer): bool =
|
||||
## IPv6 validateBuffer() implementation.
|
||||
var s = ""
|
||||
if vb.readSeq(s) > 0:
|
||||
if s.find('/') == -1:
|
||||
result = true
|
||||
pathValidateBufferNoSlash(vb)
|
||||
|
||||
proc portStB(s: string, vb: var VBuffer): bool =
|
||||
## Port number stringToBuffer() implementation.
|
||||
@@ -139,14 +170,15 @@ proc portStB(s: string, vb: var VBuffer): bool =
|
||||
port[1] = cast[byte](nport and 0xFF)
|
||||
vb.writeArray(port)
|
||||
result = true
|
||||
except:
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
proc portBtS(vb: var VBuffer, s: var string): bool =
|
||||
## Port number bufferToString() implementation.
|
||||
var port: array[2, byte]
|
||||
if vb.readArray(port) == 2:
|
||||
var nport = (cast[uint16](port[0]) shl 8) or cast[uint16](port[1])
|
||||
let nport =
|
||||
(safeConvert[uint16](port[0]) shl 8) or safeConvert[uint16](port[1])
|
||||
s = $nport
|
||||
result = true
|
||||
|
||||
@@ -164,7 +196,7 @@ proc p2pStB(s: string, vb: var VBuffer): bool =
|
||||
if MultiHash.decode(data, mh).isOk:
|
||||
vb.writeSeq(data)
|
||||
result = true
|
||||
except:
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
proc p2pBtS(vb: var VBuffer, s: var string): bool =
|
||||
@@ -199,14 +231,15 @@ proc onionStB(s: string, vb: var VBuffer): bool =
|
||||
address[11] = cast[byte](nport and 0xFF)
|
||||
vb.writeArray(address)
|
||||
result = true
|
||||
except:
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
proc onionBtS(vb: var VBuffer, s: var string): bool =
|
||||
## ONION address bufferToString() implementation.
|
||||
var buf: array[12, byte]
|
||||
if vb.readArray(buf) == 12:
|
||||
var nport = (cast[uint16](buf[10]) shl 8) or cast[uint16](buf[11])
|
||||
let nport =
|
||||
(safeConvert[uint16](buf[10]) shl 8) or safeConvert[uint16](buf[11])
|
||||
s = Base32Lower.encode(buf.toOpenArray(0, 9))
|
||||
s.add(":")
|
||||
s.add($nport)
|
||||
@@ -218,42 +251,64 @@ proc onionVB(vb: var VBuffer): bool =
|
||||
if vb.readArray(buf) == 12:
|
||||
result = true
|
||||
|
||||
proc onion3StB(s: string, vb: var VBuffer): bool =
|
||||
try:
|
||||
var parts = s.split(':')
|
||||
if len(parts) != 2:
|
||||
return false
|
||||
if len(parts[0]) != 56:
|
||||
return false
|
||||
var address = Base32Lower.decode(parts[0].toLowerAscii())
|
||||
var nport = parseInt(parts[1])
|
||||
if (nport > 0 and nport < 65536) and len(address) == 35:
|
||||
address.setLen(37)
|
||||
address[35] = cast[byte]((nport shr 8) and 0xFF)
|
||||
address[36] = cast[byte](nport and 0xFF)
|
||||
vb.writeArray(address)
|
||||
result = true
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
proc onion3BtS(vb: var VBuffer, s: var string): bool =
|
||||
## ONION address bufferToString() implementation.
|
||||
var buf: array[37, byte]
|
||||
if vb.readArray(buf) == 37:
|
||||
var nport =
|
||||
(safeConvert[uint16](buf[35]) shl 8) or safeConvert[uint16](buf[36])
|
||||
s = Base32Lower.encode(buf.toOpenArray(0, 34))
|
||||
s.add(":")
|
||||
s.add($nport)
|
||||
result = true
|
||||
|
||||
proc onion3VB(vb: var VBuffer): bool =
|
||||
## ONION address validateBuffer() implementation.
|
||||
var buf: array[37, byte]
|
||||
if vb.readArray(buf) == 37:
|
||||
result = true
|
||||
|
||||
proc unixStB(s: string, vb: var VBuffer): bool =
|
||||
## Unix socket name stringToBuffer() implementation.
|
||||
if len(s) > 0:
|
||||
vb.writeSeq(s)
|
||||
result = true
|
||||
pathStringToBuffer(s, vb)
|
||||
|
||||
proc unixBtS(vb: var VBuffer, s: var string): bool =
|
||||
## Unix socket name bufferToString() implementation.
|
||||
s = ""
|
||||
if vb.readSeq(s) > 0:
|
||||
result = true
|
||||
pathBufferToString(vb, s)
|
||||
|
||||
proc unixVB(vb: var VBuffer): bool =
|
||||
## Unix socket name validateBuffer() implementation.
|
||||
var s = ""
|
||||
if vb.readSeq(s) > 0:
|
||||
result = true
|
||||
pathValidateBuffer(vb)
|
||||
|
||||
proc dnsStB(s: string, vb: var VBuffer): bool =
|
||||
## DNS name stringToBuffer() implementation.
|
||||
if len(s) > 0:
|
||||
vb.writeSeq(s)
|
||||
result = true
|
||||
pathStringToBuffer(s, vb)
|
||||
|
||||
proc dnsBtS(vb: var VBuffer, s: var string): bool =
|
||||
## DNS name bufferToString() implementation.
|
||||
s = ""
|
||||
if vb.readSeq(s) > 0:
|
||||
result = true
|
||||
pathBufferToStringNoSlash(vb, s)
|
||||
|
||||
proc dnsVB(vb: var VBuffer): bool =
|
||||
## DNS name validateBuffer() implementation.
|
||||
var s = ""
|
||||
if vb.readSeq(s) > 0:
|
||||
if s.find('/') == -1:
|
||||
result = true
|
||||
pathValidateBufferNoSlash(vb)
|
||||
|
||||
proc mapEq*(codec: string): MaPattern =
|
||||
## ``Equal`` operator for pattern
|
||||
@@ -306,6 +361,11 @@ const
|
||||
bufferToString: onionBtS,
|
||||
validateBuffer: onionVB
|
||||
)
|
||||
TranscoderOnion3* = Transcoder(
|
||||
stringToBuffer: onion3StB,
|
||||
bufferToString: onion3BtS,
|
||||
validateBuffer: onion3VB
|
||||
)
|
||||
TranscoderDNS* = Transcoder(
|
||||
stringToBuffer: dnsStB,
|
||||
bufferToString: dnsBtS,
|
||||
@@ -351,6 +411,9 @@ const
|
||||
MAProtocol(
|
||||
mcodec: multiCodec("quic"), kind: Marker, size: 0
|
||||
),
|
||||
MAProtocol(
|
||||
mcodec: multiCodec("quic-v1"), kind: Marker, size: 0
|
||||
),
|
||||
MAProtocol(
|
||||
mcodec: multiCodec("ip6zone"), kind: Length, size: 0,
|
||||
coder: TranscoderIP6Zone
|
||||
@@ -359,12 +422,19 @@ const
|
||||
mcodec: multiCodec("onion"), kind: Fixed, size: 10,
|
||||
coder: TranscoderOnion
|
||||
),
|
||||
MAProtocol(
|
||||
mcodec: multiCodec("onion3"), kind: Fixed, size: 37,
|
||||
coder: TranscoderOnion3
|
||||
),
|
||||
MAProtocol(
|
||||
mcodec: multiCodec("ws"), kind: Marker, size: 0
|
||||
),
|
||||
MAProtocol(
|
||||
mcodec: multiCodec("wss"), kind: Marker, size: 0
|
||||
),
|
||||
MAProtocol(
|
||||
mcodec: multiCodec("tls"), kind: Marker, size: 0
|
||||
),
|
||||
MAProtocol(
|
||||
mcodec: multiCodec("ipfs"), kind: Length, size: 0,
|
||||
coder: TranscoderP2P
|
||||
@@ -415,14 +485,28 @@ const
|
||||
IP6* = mapEq("ip6")
|
||||
DNS* = mapOr(DNSANY, DNS4, DNS6, DNSADDR)
|
||||
IP* = mapOr(IP4, IP6)
|
||||
TCP* = mapOr(mapAnd(DNS, mapEq("tcp")), mapAnd(IP, mapEq("tcp")))
|
||||
UDP* = mapOr(mapAnd(DNS, mapEq("udp")), mapAnd(IP, mapEq("udp")))
|
||||
DNS_OR_IP* = mapOr(DNS, IP)
|
||||
TCP_DNS* = mapAnd(DNS, mapEq("tcp"))
|
||||
TCP_IP* = mapAnd(IP, mapEq("tcp"))
|
||||
TCP* = mapOr(TCP_DNS, TCP_IP)
|
||||
UDP_DNS* = mapAnd(DNS, mapEq("udp"))
|
||||
UDP_IP* = mapAnd(IP, mapEq("udp"))
|
||||
UDP* = mapOr(UDP_DNS, UDP_IP)
|
||||
UTP* = mapAnd(UDP, mapEq("utp"))
|
||||
QUIC* = mapAnd(UDP, mapEq("quic"))
|
||||
UNIX* = mapEq("unix")
|
||||
WS_DNS* = mapAnd(TCP_DNS, mapEq("ws"))
|
||||
WS_IP* = mapAnd(TCP_IP, mapEq("ws"))
|
||||
WS* = mapAnd(TCP, mapEq("ws"))
|
||||
WSS* = mapAnd(TCP, mapEq("wss"))
|
||||
TLS_WS* = mapOr(mapEq("wss"), mapAnd(mapEq("tls"), mapEq("ws")))
|
||||
WSS_DNS* = mapAnd(TCP_DNS, TLS_WS)
|
||||
WSS_IP* = mapAnd(TCP_IP, TLS_WS)
|
||||
WSS* = mapAnd(TCP, TLS_WS)
|
||||
WebSockets_DNS* = mapOr(WS_DNS, WSS_DNS)
|
||||
WebSockets_IP* = mapOr(WS_IP, WSS_IP)
|
||||
WebSockets* = mapOr(WS, WSS)
|
||||
Onion3* = mapEq("onion3")
|
||||
TcpOnion3* = mapAnd(TCP, Onion3)
|
||||
|
||||
Unreliable* = mapOr(UDP)
|
||||
|
||||
@@ -469,15 +553,10 @@ proc trimRight(s: string, ch: char): string =
|
||||
break
|
||||
result = s[0..(s.high - m)]
|
||||
|
||||
proc shcopy*(m1: var MultiAddress, m2: MultiAddress) =
|
||||
shallowCopy(m1.data.buffer, m2.data.buffer)
|
||||
m1.data.offset = m2.data.offset
|
||||
|
||||
proc protoCode*(ma: MultiAddress): MaResult[MultiCodec] =
|
||||
## Returns MultiAddress ``ma`` protocol code.
|
||||
var header: uint64
|
||||
var vb: MultiAddress
|
||||
shcopy(vb, ma)
|
||||
var vb = ma
|
||||
if vb.data.readVarint(header) == -1:
|
||||
err("multiaddress: Malformed binary address!")
|
||||
else:
|
||||
@@ -490,8 +569,7 @@ proc protoCode*(ma: MultiAddress): MaResult[MultiCodec] =
|
||||
proc protoName*(ma: MultiAddress): MaResult[string] =
|
||||
## Returns MultiAddress ``ma`` protocol name.
|
||||
var header: uint64
|
||||
var vb: MultiAddress
|
||||
shcopy(vb, ma)
|
||||
var vb = ma
|
||||
if vb.data.readVarint(header) == -1:
|
||||
err("multiaddress: Malformed binary address!")
|
||||
else:
|
||||
@@ -508,9 +586,8 @@ proc protoArgument*(ma: MultiAddress,
|
||||
## If current MultiAddress do not have argument value, then result will be
|
||||
## ``0``.
|
||||
var header: uint64
|
||||
var vb: MultiAddress
|
||||
var vb = ma
|
||||
var buffer: seq[byte]
|
||||
shcopy(vb, ma)
|
||||
if vb.data.readVarint(header) == -1:
|
||||
err("multiaddress: Malformed binary address!")
|
||||
else:
|
||||
@@ -526,7 +603,7 @@ proc protoArgument*(ma: MultiAddress,
|
||||
err("multiaddress: Decoding protocol error")
|
||||
else:
|
||||
ok(res)
|
||||
elif proto.kind in {Length, Path}:
|
||||
elif proto.kind in {MAKind.Length, Path}:
|
||||
if vb.data.readSeq(buffer) == -1:
|
||||
err("multiaddress: Decoding protocol error")
|
||||
else:
|
||||
@@ -547,6 +624,13 @@ proc protoAddress*(ma: MultiAddress): MaResult[seq[byte]] =
|
||||
buffer.setLen(res)
|
||||
ok(buffer)
|
||||
|
||||
proc protoArgument*(ma: MultiAddress): MaResult[seq[byte]] =
|
||||
## Returns MultiAddress ``ma`` protocol address binary blob.
|
||||
##
|
||||
## If current MultiAddress do not have argument value, then result array will
|
||||
## be empty.
|
||||
ma.protoAddress()
|
||||
|
||||
proc getPart(ma: MultiAddress, index: int): MaResult[MultiAddress] =
|
||||
var header: uint64
|
||||
var data = newSeq[byte]()
|
||||
@@ -554,6 +638,9 @@ proc getPart(ma: MultiAddress, index: int): MaResult[MultiAddress] =
|
||||
var vb = ma
|
||||
var res: MultiAddress
|
||||
res.data = initVBuffer()
|
||||
|
||||
if index < 0: return err("multiaddress: negative index gived to getPart")
|
||||
|
||||
while offset <= index:
|
||||
if vb.data.readVarint(header) == -1:
|
||||
return err("multiaddress: Malformed binary address!")
|
||||
@@ -571,7 +658,7 @@ proc getPart(ma: MultiAddress, index: int): MaResult[MultiAddress] =
|
||||
res.data.writeVarint(header)
|
||||
res.data.writeArray(data)
|
||||
res.data.finish()
|
||||
elif proto.kind in {Length, Path}:
|
||||
elif proto.kind in {MAKind.Length, Path}:
|
||||
if vb.data.readSeq(data) == -1:
|
||||
return err("multiaddress: Decoding protocol error")
|
||||
|
||||
@@ -586,9 +673,33 @@ proc getPart(ma: MultiAddress, index: int): MaResult[MultiAddress] =
|
||||
inc(offset)
|
||||
ok(res)
|
||||
|
||||
proc `[]`*(ma: MultiAddress, i: int): MaResult[MultiAddress] {.inline.} =
|
||||
proc getParts[U, V](ma: MultiAddress,
|
||||
slice: HSlice[U, V]): MaResult[MultiAddress] =
|
||||
when slice.a is BackwardsIndex or slice.b is BackwardsIndex:
|
||||
let maLength = ? len(ma)
|
||||
template normalizeIndex(index): int =
|
||||
when index is BackwardsIndex: maLength - int(index)
|
||||
else: int(index)
|
||||
let
|
||||
indexStart = normalizeIndex(slice.a)
|
||||
indexEnd = normalizeIndex(slice.b)
|
||||
var res: MultiAddress
|
||||
for i in indexStart..indexEnd:
|
||||
? res.append(? ma[i])
|
||||
ok(res)
|
||||
|
||||
proc `[]`*(ma: MultiAddress,
|
||||
i: int | BackwardsIndex): MaResult[MultiAddress] {.inline.} =
|
||||
## Returns part with index ``i`` of MultiAddress ``ma``.
|
||||
ma.getPart(i)
|
||||
when i is BackwardsIndex:
|
||||
let maLength = ? len(ma)
|
||||
ma.getPart(maLength - int(i))
|
||||
else:
|
||||
ma.getPart(i)
|
||||
|
||||
proc `[]`*(ma: MultiAddress, slice: HSlice): MaResult[MultiAddress] {.inline.} =
|
||||
## Returns parts with slice ``slice`` of MultiAddress ``ma``.
|
||||
ma.getParts(slice)
|
||||
|
||||
iterator items*(ma: MultiAddress): MaResult[MultiAddress] =
|
||||
## Iterates over all addresses inside of MultiAddress ``ma``.
|
||||
@@ -615,7 +726,7 @@ iterator items*(ma: MultiAddress): MaResult[MultiAddress] =
|
||||
|
||||
res.data.writeVarint(header)
|
||||
res.data.writeArray(data)
|
||||
elif proto.kind in {Length, Path}:
|
||||
elif proto.kind in {MAKind.Length, Path}:
|
||||
if vb.data.readSeq(data) == -1:
|
||||
yield err(MaResult[MultiAddress], "Decoding protocol error")
|
||||
|
||||
@@ -626,6 +737,13 @@ iterator items*(ma: MultiAddress): MaResult[MultiAddress] =
|
||||
res.data.finish()
|
||||
yield ok(MaResult[MultiAddress], res)
|
||||
|
||||
proc len*(ma: MultiAddress): MaResult[int] =
|
||||
var counter: int
|
||||
for part in ma:
|
||||
if part.isErr: return err(part.error)
|
||||
counter.inc()
|
||||
ok(counter)
|
||||
|
||||
proc contains*(ma: MultiAddress, codec: MultiCodec): MaResult[bool] {.inline.} =
|
||||
## Returns ``true``, if address with MultiCodec ``codec`` present in
|
||||
## MultiAddress ``ma``.
|
||||
@@ -666,7 +784,7 @@ proc toString*(value: MultiAddress): MaResult[string] =
|
||||
if not proto.coder.bufferToString(vb.data, part):
|
||||
return err("multiaddress: Decoding protocol error")
|
||||
parts.add($(proto.mcodec))
|
||||
if proto.kind == Path and part[0] == '/':
|
||||
if len(part) > 0 and (proto.kind == Path) and (part[0] == '/'):
|
||||
parts.add(part[1..^1])
|
||||
else:
|
||||
parts.add(part)
|
||||
@@ -676,7 +794,7 @@ proc toString*(value: MultiAddress): MaResult[string] =
|
||||
res = "/" & parts.join("/")
|
||||
ok(res)
|
||||
|
||||
proc `$`*(value: MultiAddress): string {.raises: [Defect].} =
|
||||
proc `$`*(value: MultiAddress): string =
|
||||
## Return string representation of MultiAddress ``value``.
|
||||
let s = value.toString()
|
||||
if s.isErr: s.error
|
||||
@@ -706,8 +824,7 @@ proc encode*(mbtype: typedesc[MultiBase], encoding: string,
|
||||
proc validate*(ma: MultiAddress): bool =
|
||||
## Returns ``true`` if MultiAddress ``ma`` is valid.
|
||||
var header: uint64
|
||||
var vb: MultiAddress
|
||||
shcopy(vb, ma)
|
||||
var vb = ma
|
||||
while true:
|
||||
if vb.data.isEmpty():
|
||||
break
|
||||
@@ -792,6 +909,8 @@ proc getProtocol(name: string): MAProtocol {.inline.} =
|
||||
proc init*(mtype: typedesc[MultiAddress],
|
||||
value: string): MaResult[MultiAddress] =
|
||||
## Initialize MultiAddress object from string representation ``value``.
|
||||
if len(value) == 0 or value == "/":
|
||||
return err("multiaddress: Address must not be empty!")
|
||||
var parts = value.trimRight('/').split('/')
|
||||
if len(parts[0]) != 0:
|
||||
err("multiaddress: Invalid MultiAddress, must start with `/`")
|
||||
@@ -839,7 +958,7 @@ proc init*(mtype: typedesc[MultiAddress],
|
||||
data: openArray[byte]): MaResult[MultiAddress] =
|
||||
## Initialize MultiAddress with array of bytes ``data``.
|
||||
if len(data) == 0:
|
||||
err("multiaddress: Address could not be empty!")
|
||||
err("multiaddress: Address must not be empty!")
|
||||
else:
|
||||
var res: MultiAddress
|
||||
res.data = initVBuffer()
|
||||
@@ -854,7 +973,7 @@ proc init*(mtype: typedesc[MultiAddress]): MultiAddress =
|
||||
## Initialize empty MultiAddress.
|
||||
result.data = initVBuffer()
|
||||
|
||||
proc init*(mtype: typedesc[MultiAddress], address: ValidIpAddress,
|
||||
proc init*(mtype: typedesc[MultiAddress], address: IpAddress,
|
||||
protocol: IpTransportProtocol, port: Port): MultiAddress =
|
||||
var res: MultiAddress
|
||||
res.data = initVBuffer()
|
||||
@@ -925,7 +1044,7 @@ proc append*(m1: var MultiAddress, m2: MultiAddress): MaResult[void] =
|
||||
ok()
|
||||
|
||||
proc `&`*(m1, m2: MultiAddress): MultiAddress {.
|
||||
raises: [Defect, LPError].} =
|
||||
raises: [LPError].} =
|
||||
## Concatenates two addresses ``m1`` and ``m2``, and returns result.
|
||||
##
|
||||
## This procedure performs validation of concatenated result and can raise
|
||||
@@ -935,7 +1054,7 @@ proc `&`*(m1, m2: MultiAddress): MultiAddress {.
|
||||
concat(m1, m2).tryGet()
|
||||
|
||||
proc `&=`*(m1: var MultiAddress, m2: MultiAddress) {.
|
||||
raises: [Defect, LPError].} =
|
||||
raises: [LPError].} =
|
||||
## Concatenates two addresses ``m1`` and ``m2``.
|
||||
##
|
||||
## This procedure performs validation of concatenated result and can raise
|
||||
@@ -979,19 +1098,15 @@ proc matchPart(pat: MaPattern, protos: seq[MultiCodec]): MaPatResult =
|
||||
proc match*(pat: MaPattern, address: MultiAddress): bool =
|
||||
## Match full ``address`` using pattern ``pat`` and return ``true`` if
|
||||
## ``address`` satisfies pattern.
|
||||
let protos = address.protocols()
|
||||
if protos.isErr():
|
||||
return false
|
||||
let res = matchPart(pat, protos.get())
|
||||
let protos = address.protocols().valueOr: return false
|
||||
let res = matchPart(pat, protos)
|
||||
res.flag and (len(res.rem) == 0)
|
||||
|
||||
proc matchPartial*(pat: MaPattern, address: MultiAddress): bool =
|
||||
## Match prefix part of ``address`` using pattern ``pat`` and return
|
||||
## ``true`` if ``address`` starts with pattern.
|
||||
let protos = address.protocols()
|
||||
if protos.isErr():
|
||||
return false
|
||||
let res = matchPart(pat, protos.get())
|
||||
let protos = address.protocols().valueOr: return false
|
||||
let res = matchPart(pat, protos)
|
||||
res.flag
|
||||
|
||||
proc `$`*(pat: MaPattern): string =
|
||||
@@ -1006,6 +1121,9 @@ proc `$`*(pat: MaPattern): string =
|
||||
elif pat.operator == Eq:
|
||||
result = $pat.value
|
||||
|
||||
proc bytes*(value: MultiAddress): seq[byte] =
|
||||
value.data.buffer
|
||||
|
||||
proc write*(pb: var ProtoBuffer, field: int, value: MultiAddress) {.inline.} =
|
||||
write(pb, field, value.data.buffer)
|
||||
|
||||
@@ -1017,16 +1135,20 @@ proc getField*(pb: ProtoBuffer, field: int,
|
||||
if not(res):
|
||||
ok(false)
|
||||
else:
|
||||
let ma = MultiAddress.init(buffer)
|
||||
if ma.isOk():
|
||||
value = ma.get()
|
||||
ok(true)
|
||||
else:
|
||||
err(ProtoError.IncorrectBlob)
|
||||
value = MultiAddress.init(buffer).valueOr:
|
||||
return err(ProtoError.IncorrectBlob)
|
||||
ok(true)
|
||||
|
||||
proc getRepeatedField*(pb: ProtoBuffer, field: int,
|
||||
value: var seq[MultiAddress]): ProtoResult[bool] {.
|
||||
inline.} =
|
||||
## Read repeated field from protobuf message. ``field`` is field number.
|
||||
## If the message is malformed, an error is returned. If field is not present
|
||||
## in message, then ``ok(false)`` is returned and value is empty. If field is
|
||||
## present, but no items could be parsed, then
|
||||
## ``err(ProtoError.IncorrectBlob)`` is returned and value is empty.
|
||||
## If field is present and some item could be parsed, then ``true`` is
|
||||
## returned and value contains the parsed values.
|
||||
var items: seq[seq[byte]]
|
||||
value.setLen(0)
|
||||
let res = ? pb.getRepeatedField(field, items)
|
||||
@@ -1034,10 +1156,12 @@ proc getRepeatedField*(pb: ProtoBuffer, field: int,
|
||||
ok(false)
|
||||
else:
|
||||
for item in items:
|
||||
let ma = MultiAddress.init(item)
|
||||
if ma.isOk():
|
||||
value.add(ma.get())
|
||||
else:
|
||||
value.setLen(0)
|
||||
return err(ProtoError.IncorrectBlob)
|
||||
ok(true)
|
||||
let ma = MultiAddress.init(item).valueOr:
|
||||
debug "Unsupported MultiAddress in blob", ma = item
|
||||
continue
|
||||
|
||||
value.add(ma)
|
||||
if value.len == 0:
|
||||
err(ProtoError.IncorrectBlob)
|
||||
else:
|
||||
ok(true)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements MultiBase.
|
||||
##
|
||||
@@ -13,7 +13,7 @@
|
||||
## 1. base32z
|
||||
##
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import tables
|
||||
import stew/[base32, base58, base64, results]
|
||||
@@ -24,17 +24,17 @@ type
|
||||
|
||||
MultiBase* = object
|
||||
|
||||
MBCodeSize = proc(length: int): int {.nimcall, gcsafe, noSideEffect, raises: [Defect].}
|
||||
MBCodeSize = proc(length: int): int {.nimcall, gcsafe, noSideEffect, raises: [].}
|
||||
|
||||
MBCodec = object
|
||||
code: char
|
||||
name: string
|
||||
encr: proc(inbytes: openArray[byte],
|
||||
outbytes: var openArray[char],
|
||||
outlen: var int): MultiBaseStatus {.nimcall, gcsafe, noSideEffect, raises: [Defect].}
|
||||
outlen: var int): MultiBaseStatus {.nimcall, gcsafe, noSideEffect, raises: [].}
|
||||
decr: proc(inbytes: openArray[char],
|
||||
outbytes: var openArray[byte],
|
||||
outlen: var int): MultiBaseStatus {.nimcall, gcsafe, noSideEffect, raises: [Defect].}
|
||||
outlen: var int): MultiBaseStatus {.nimcall, gcsafe, noSideEffect, raises: [].}
|
||||
encl: MBCodeSize
|
||||
decl: MBCodeSize
|
||||
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not BE copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not BE copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements MultiCodec.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import tables, hashes
|
||||
import varint, vbuffer
|
||||
import stew/results
|
||||
export results
|
||||
|
||||
{.deadCodeElim: on.}
|
||||
|
||||
## List of officially supported codecs can BE found here
|
||||
## https://github.com/multiformats/multicodec/blob/master/table.csv
|
||||
const MultiCodecList = [
|
||||
@@ -193,13 +191,16 @@ const MultiCodecList = [
|
||||
("p2p", 0x01A5),
|
||||
("http", 0x01E0),
|
||||
("https", 0x01BB),
|
||||
("tls", 0x01C0),
|
||||
("quic", 0x01CC),
|
||||
("quic-v1", 0x01CD),
|
||||
("ws", 0x01DD),
|
||||
("wss", 0x01DE), # not in multicodec list
|
||||
("wss", 0x01DE),
|
||||
("p2p-websocket-star", 0x01DF), # not in multicodec list
|
||||
("p2p-webrtc-star", 0x0113), # not in multicodec list
|
||||
("p2p-webrtc-direct", 0x0114), # not in multicodec list
|
||||
("onion", 0x01BC),
|
||||
("onion3", 0x01BD),
|
||||
("p2p-circuit", 0x0122),
|
||||
("libp2p-peer-record", 0x0301),
|
||||
("dns", 0x35),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements MultiHash.
|
||||
## Supported hashes are:
|
||||
@@ -21,7 +21,7 @@
|
||||
## 1. SKEIN
|
||||
## 2. MURMUR
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import tables
|
||||
import nimcrypto/[sha, sha2, keccak, blake2, hash, utils]
|
||||
@@ -42,7 +42,7 @@ const
|
||||
|
||||
type
|
||||
MHashCoderProc* = proc(data: openArray[byte],
|
||||
output: var openArray[byte]) {.nimcall, gcsafe, noSideEffect, raises: [Defect].}
|
||||
output: var openArray[byte]) {.nimcall, gcsafe, noSideEffect, raises: [].}
|
||||
MHash* = object
|
||||
mcodec*: MultiCodec
|
||||
size*: int
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2019 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[strutils]
|
||||
import std/[strutils, sequtils, tables]
|
||||
import chronos, chronicles, stew/byteutils
|
||||
import stream/connection,
|
||||
protocols/protocol
|
||||
@@ -18,43 +18,48 @@ logScope:
|
||||
topics = "libp2p multistream"
|
||||
|
||||
const
|
||||
MsgSize* = 64*1024
|
||||
Codec* = "/multistream/1.0.0"
|
||||
MsgSize = 1024
|
||||
Codec = "/multistream/1.0.0"
|
||||
|
||||
MSCodec* = "\x13" & Codec & "\n"
|
||||
Na* = "\x03na\n"
|
||||
Ls* = "\x03ls\n"
|
||||
Na = "na\n"
|
||||
Ls = "ls\n"
|
||||
|
||||
type
|
||||
Matcher* = proc (proto: string): bool {.gcsafe, raises: [Defect].}
|
||||
Matcher* = proc (proto: string): bool {.gcsafe, raises: [].}
|
||||
|
||||
MultiStreamError* = object of LPError
|
||||
|
||||
HandlerHolder* = object
|
||||
HandlerHolder* = ref object
|
||||
protos*: seq[string]
|
||||
protocol*: LPProtocol
|
||||
match*: Matcher
|
||||
openedStreams: CountTable[PeerId]
|
||||
|
||||
MultistreamSelect* = ref object of RootObj
|
||||
handlers*: seq[HandlerHolder]
|
||||
codec*: string
|
||||
|
||||
proc new*(T: typedesc[MultistreamSelect]): T =
|
||||
T(codec: MSCodec)
|
||||
T(
|
||||
codec: Codec,
|
||||
)
|
||||
|
||||
template validateSuffix(str: string): untyped =
|
||||
if str.endsWith("\n"):
|
||||
str.removeSuffix("\n")
|
||||
else:
|
||||
raise newException(MultiStreamError, "MultistreamSelect failed, malformed message")
|
||||
if str.endsWith("\n"):
|
||||
str.removeSuffix("\n")
|
||||
else:
|
||||
raise (ref MultiStreamError)(msg:
|
||||
"MultistreamSelect failed, malformed message")
|
||||
|
||||
proc select*(m: MultistreamSelect,
|
||||
conn: Connection,
|
||||
proto: seq[string]):
|
||||
Future[string] {.async.} =
|
||||
trace "initiating handshake", conn, codec = m.codec
|
||||
proc select*(
|
||||
_: MultistreamSelect | type MultistreamSelect,
|
||||
conn: Connection,
|
||||
proto: seq[string]
|
||||
): Future[string] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MultiStreamError]).} =
|
||||
trace "initiating handshake", conn, codec = Codec
|
||||
## select a remote protocol
|
||||
await conn.write(m.codec) # write handshake
|
||||
await conn.writeLp(Codec & "\n") # write handshake
|
||||
if proto.len() > 0:
|
||||
trace "selecting proto", conn, proto = proto[0]
|
||||
await conn.writeLp((proto[0] & "\n")) # select proto
|
||||
@@ -64,7 +69,7 @@ proc select*(m: MultistreamSelect,
|
||||
|
||||
if s != Codec:
|
||||
notice "handshake failed", conn, codec = s
|
||||
raise newException(MultiStreamError, "MultistreamSelect handshake failed")
|
||||
raise (ref MultiStreamError)(msg: "MultistreamSelect handshake failed")
|
||||
else:
|
||||
trace "multistream handshake success", conn
|
||||
|
||||
@@ -76,7 +81,7 @@ proc select*(m: MultistreamSelect,
|
||||
trace "reading first requested proto", conn
|
||||
if s == proto[0]:
|
||||
trace "successfully selected ", conn, proto = proto[0]
|
||||
conn.tag = proto[0]
|
||||
conn.protocol = proto[0]
|
||||
return proto[0]
|
||||
elif proto.len > 1:
|
||||
# Try to negotiate alternatives
|
||||
@@ -89,31 +94,41 @@ proc select*(m: MultistreamSelect,
|
||||
validateSuffix(s)
|
||||
if s == p:
|
||||
trace "selected protocol", conn, protocol = s
|
||||
conn.tag = s
|
||||
conn.protocol = s
|
||||
return s
|
||||
return ""
|
||||
else:
|
||||
# No alternatives, fail
|
||||
return ""
|
||||
|
||||
proc select*(m: MultistreamSelect,
|
||||
conn: Connection,
|
||||
proto: string): Future[bool] {.async.} =
|
||||
proc select*(
|
||||
_: MultistreamSelect | type MultistreamSelect,
|
||||
conn: Connection,
|
||||
proto: string
|
||||
): Future[bool] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MultiStreamError]).} =
|
||||
if proto.len > 0:
|
||||
return (await m.select(conn, @[proto])) == proto
|
||||
(await MultistreamSelect.select(conn, @[proto])) == proto
|
||||
else:
|
||||
return (await m.select(conn, @[])) == Codec
|
||||
(await MultistreamSelect.select(conn, @[])) == Codec
|
||||
|
||||
proc select*(m: MultistreamSelect, conn: Connection): Future[bool] =
|
||||
proc select*(
|
||||
m: MultistreamSelect,
|
||||
conn: Connection
|
||||
): Future[bool] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MultiStreamError], raw: true).} =
|
||||
m.select(conn, "")
|
||||
|
||||
proc list*(m: MultistreamSelect,
|
||||
conn: Connection): Future[seq[string]] {.async.} =
|
||||
proc list*(
|
||||
m: MultistreamSelect,
|
||||
conn: Connection
|
||||
): Future[seq[string]] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MultiStreamError]).} =
|
||||
## list remote protos requests on connection
|
||||
if not await m.select(conn):
|
||||
return
|
||||
|
||||
await conn.write(Ls) # send ls
|
||||
await conn.writeLp(Ls) # send ls
|
||||
|
||||
var list = newSeq[string]()
|
||||
let ms = string.fromBytes(await conn.readLp(MsgSize))
|
||||
@@ -123,55 +138,90 @@ proc list*(m: MultistreamSelect,
|
||||
|
||||
result = list
|
||||
|
||||
proc handle*(m: MultistreamSelect, conn: Connection, active: bool = false) {.async, gcsafe.} =
|
||||
trace "Starting multistream handler", conn, handshaked = active
|
||||
proc handle*(
|
||||
_: type MultistreamSelect,
|
||||
conn: Connection,
|
||||
protos: seq[string],
|
||||
matchers = newSeq[Matcher](),
|
||||
active: bool = false
|
||||
): Future[string] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MultiStreamError]).} =
|
||||
trace "Starting multistream negotiation", conn, handshaked = active
|
||||
var handshaked = active
|
||||
try:
|
||||
while not conn.atEof:
|
||||
var ms = string.fromBytes(await conn.readLp(MsgSize))
|
||||
validateSuffix(ms)
|
||||
while not conn.atEof:
|
||||
var ms = string.fromBytes(await conn.readLp(MsgSize))
|
||||
validateSuffix(ms)
|
||||
|
||||
if not handshaked and ms != Codec:
|
||||
notice "expected handshake message", conn, instead=ms
|
||||
raise newException(CatchableError,
|
||||
"MultistreamSelect handling failed, invalid first message")
|
||||
if not handshaked and ms != Codec:
|
||||
debug "expected handshake message", conn, instead=ms
|
||||
raise (ref MultiStreamError)(msg:
|
||||
"MultistreamSelect handling failed, invalid first message")
|
||||
|
||||
trace "handle: got request", conn, ms
|
||||
if ms.len() <= 0:
|
||||
trace "handle: invalid proto", conn
|
||||
await conn.write(Na)
|
||||
trace "handle: got request", conn, ms
|
||||
if ms.len() <= 0:
|
||||
trace "handle: invalid proto", conn
|
||||
await conn.writeLp(Na)
|
||||
|
||||
if m.handlers.len() == 0:
|
||||
trace "handle: sending `na` for protocol", conn, protocol = ms
|
||||
await conn.write(Na)
|
||||
continue
|
||||
|
||||
case ms:
|
||||
of "ls":
|
||||
trace "handle: listing protos", conn
|
||||
var protos = ""
|
||||
for h in m.handlers:
|
||||
for proto in h.protos:
|
||||
protos &= (proto & "\n")
|
||||
await conn.writeLp(protos)
|
||||
of Codec:
|
||||
if not handshaked:
|
||||
await conn.write(m.codec)
|
||||
handshaked = true
|
||||
else:
|
||||
trace "handle: sending `na` for duplicate handshake while handshaked",
|
||||
conn
|
||||
await conn.write(Na)
|
||||
case ms:
|
||||
of "ls":
|
||||
trace "handle: listing protos", conn
|
||||
#TODO this doens't seem to follow spec, each protocol
|
||||
# should be length prefixed. Not very important
|
||||
# since LS is getting deprecated
|
||||
await conn.writeLp(protos.join("\n") & "\n")
|
||||
of Codec:
|
||||
if not handshaked:
|
||||
await conn.writeLp(Codec & "\n")
|
||||
handshaked = true
|
||||
else:
|
||||
for h in m.handlers:
|
||||
if (not isNil(h.match) and h.match(ms)) or h.protos.contains(ms):
|
||||
trace "found handler", conn, protocol = ms
|
||||
await conn.writeLp(ms & "\n")
|
||||
conn.tag = ms
|
||||
await h.protocol.handler(conn, ms)
|
||||
return
|
||||
debug "no handlers", conn, protocol = ms
|
||||
await conn.write(Na)
|
||||
trace "handle: sending `na` for duplicate handshake while handshaked",
|
||||
conn
|
||||
await conn.writeLp(Na)
|
||||
elif ms in protos or matchers.anyIt(it(ms)):
|
||||
trace "found handler", conn, protocol = ms
|
||||
await conn.writeLp(ms & "\n")
|
||||
conn.protocol = ms
|
||||
return ms
|
||||
else:
|
||||
trace "no handlers", conn, protocol = ms
|
||||
await conn.writeLp(Na)
|
||||
|
||||
proc handle*(
|
||||
m: MultistreamSelect,
|
||||
conn: Connection,
|
||||
active: bool = false) {.async: (raises: [CancelledError]).} =
|
||||
trace "Starting multistream handler", conn, handshaked = active
|
||||
var
|
||||
protos: seq[string]
|
||||
matchers: seq[Matcher]
|
||||
for h in m.handlers:
|
||||
if h.match != nil:
|
||||
matchers.add(h.match)
|
||||
for proto in h.protos:
|
||||
protos.add(proto)
|
||||
|
||||
try:
|
||||
let ms = await MultistreamSelect.handle(conn, protos, matchers, active)
|
||||
for h in m.handlers:
|
||||
if (h.match != nil and h.match(ms)) or h.protos.contains(ms):
|
||||
trace "found handler", conn, protocol = ms
|
||||
|
||||
var protocolHolder = h
|
||||
let maxIncomingStreams = protocolHolder.protocol.maxIncomingStreams
|
||||
if protocolHolder.openedStreams.getOrDefault(conn.peerId) >=
|
||||
maxIncomingStreams:
|
||||
debug "Max streams for protocol reached, blocking new stream",
|
||||
conn, protocol = ms, maxIncomingStreams
|
||||
return
|
||||
protocolHolder.openedStreams.inc(conn.peerId)
|
||||
try:
|
||||
await protocolHolder.protocol.handler(conn, ms)
|
||||
finally:
|
||||
protocolHolder.openedStreams.inc(conn.peerId, -1)
|
||||
if protocolHolder.openedStreams[conn.peerId] == 0:
|
||||
protocolHolder.openedStreams.del(conn.peerId)
|
||||
return
|
||||
debug "no handlers", conn, ms
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
@@ -196,10 +246,14 @@ proc addHandler*(m: MultistreamSelect,
|
||||
matcher: Matcher = nil) =
|
||||
addHandler(m, @[codec], protocol, matcher)
|
||||
|
||||
proc addHandler*(m: MultistreamSelect,
|
||||
codec: string,
|
||||
handler: LPProtoHandler,
|
||||
matcher: Matcher = nil) =
|
||||
proc addHandler*[E](
|
||||
m: MultistreamSelect,
|
||||
codec: string,
|
||||
handler: LPProtoHandler |
|
||||
proc (
|
||||
conn: Connection,
|
||||
proto: string): InternalRaisesFuture[void, E],
|
||||
matcher: Matcher = nil) =
|
||||
## helper to allow registering pure handlers
|
||||
trace "registering proto handler", proto = codec
|
||||
let protocol = new LPProtocol
|
||||
@@ -209,3 +263,35 @@ proc addHandler*(m: MultistreamSelect,
|
||||
m.handlers.add(HandlerHolder(protos: @[codec],
|
||||
protocol: protocol,
|
||||
match: matcher))
|
||||
|
||||
proc start*(m: MultistreamSelect) {.async: (raises: [CancelledError]).} =
|
||||
# Nim 1.6.18: Using `mapIt` results in a seq of `.Raising([])`
|
||||
# TODO https://github.com/nim-lang/Nim/issues/23445
|
||||
var futs = newSeqOfCap[Future[void].Raising([CancelledError])](m.handlers.len)
|
||||
for it in m.handlers:
|
||||
futs.add it.protocol.start()
|
||||
try:
|
||||
await allFutures(futs)
|
||||
for fut in futs:
|
||||
await fut
|
||||
except CancelledError as exc:
|
||||
var pending: seq[Future[void].Raising([])]
|
||||
doAssert m.handlers.len == futs.len, "Handlers modified while starting"
|
||||
for i, fut in futs:
|
||||
if not fut.finished:
|
||||
pending.add fut.cancelAndWait()
|
||||
elif fut.completed:
|
||||
pending.add m.handlers[i].protocol.stop()
|
||||
else:
|
||||
static: doAssert typeof(fut).E is (CancelledError,)
|
||||
await noCancel allFutures(pending)
|
||||
raise exc
|
||||
|
||||
proc stop*(m: MultistreamSelect) {.async: (raises: []).} =
|
||||
# Nim 1.6.18: Using `mapIt` results in a seq of `.Raising([CancelledError])`
|
||||
var futs = newSeqOfCap[Future[void].Raising([])](m.handlers.len)
|
||||
for it in m.handlers:
|
||||
futs.add it.protocol.stop()
|
||||
await noCancel allFutures(futs)
|
||||
for fut in futs:
|
||||
await fut
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2019 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import pkg/[chronos, nimcrypto/utils, chronicles, stew/byteutils]
|
||||
import pkg/[chronos, chronicles, stew/byteutils]
|
||||
import ../../stream/connection,
|
||||
../../utility,
|
||||
../../varint,
|
||||
@@ -42,7 +42,10 @@ const MaxMsgSize* = 1 shl 20 # 1mb
|
||||
proc newInvalidMplexMsgType*(): ref InvalidMplexMsgType =
|
||||
newException(InvalidMplexMsgType, "invalid message type")
|
||||
|
||||
proc readMsg*(conn: Connection): Future[Msg] {.async, gcsafe.} =
|
||||
proc readMsg*(
|
||||
conn: Connection
|
||||
): Future[Msg] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MuxerError]).} =
|
||||
let header = await conn.readVarint()
|
||||
trace "read header varint", varint = header, conn
|
||||
|
||||
@@ -55,10 +58,13 @@ proc readMsg*(conn: Connection): Future[Msg] {.async, gcsafe.} =
|
||||
|
||||
return (header shr 3, MessageType(msgType), data)
|
||||
|
||||
proc writeMsg*(conn: Connection,
|
||||
id: uint64,
|
||||
msgType: MessageType,
|
||||
data: seq[byte] = @[]): Future[void] =
|
||||
proc writeMsg*(
|
||||
conn: Connection,
|
||||
id: uint64,
|
||||
msgType: MessageType,
|
||||
data: seq[byte] = @[]
|
||||
): Future[void] {.async: (raises: [
|
||||
CancelledError, LPStreamError], raw: true).} =
|
||||
var
|
||||
left = data.len
|
||||
offset = 0
|
||||
@@ -84,8 +90,11 @@ proc writeMsg*(conn: Connection,
|
||||
# message gets written before some of the chunks
|
||||
conn.write(buf.buffer)
|
||||
|
||||
proc writeMsg*(conn: Connection,
|
||||
id: uint64,
|
||||
msgType: MessageType,
|
||||
data: string): Future[void] =
|
||||
proc writeMsg*(
|
||||
conn: Connection,
|
||||
id: uint64,
|
||||
msgType: MessageType,
|
||||
data: string
|
||||
): Future[void] {.async: (raises: [
|
||||
CancelledError, LPStreamError], raw: true).} =
|
||||
conn.writeMsg(id, msgType, data.toBytes())
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2019 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[oids, strformat]
|
||||
import pkg/[chronos, chronicles, metrics, nimcrypto/utils]
|
||||
import pkg/[chronos, chronicles, metrics]
|
||||
import ./coder,
|
||||
../muxer,
|
||||
../../stream/[bufferstream, connection, streamseq],
|
||||
@@ -28,7 +28,8 @@ when defined(libp2p_mplex_metrics):
|
||||
declareHistogram libp2p_mplex_qtime, "message queuing time"
|
||||
|
||||
when defined(libp2p_network_protocols_metrics):
|
||||
declareCounter libp2p_protocols_bytes, "total sent or received bytes", ["protocol", "direction"]
|
||||
declareCounter libp2p_protocols_bytes,
|
||||
"total sent or received bytes", ["protocol", "direction"]
|
||||
|
||||
## Channel half-closed states
|
||||
##
|
||||
@@ -55,6 +56,8 @@ type
|
||||
initiator*: bool # initiated remotely or locally flag
|
||||
isOpen*: bool # has channel been opened
|
||||
closedLocal*: bool # has channel been closed locally
|
||||
remoteReset*: bool # has channel been remotely reset
|
||||
localReset*: bool # has channel been reset locally
|
||||
msgCode*: MessageType # cached in/out message code
|
||||
closeCode*: MessageType # cached in/out close code
|
||||
resetCode*: MessageType # cached in/out reset code
|
||||
@@ -62,63 +65,65 @@ type
|
||||
|
||||
func shortLog*(s: LPChannel): auto =
|
||||
try:
|
||||
if s.isNil: "LPChannel(nil)"
|
||||
if s == nil: "LPChannel(nil)"
|
||||
elif s.name != $s.oid and s.name.len > 0:
|
||||
&"{shortLog(s.conn.peerId)}:{s.oid}:{s.name}"
|
||||
else: &"{shortLog(s.conn.peerId)}:{s.oid}"
|
||||
except ValueError as exc:
|
||||
raise newException(Defect, exc.msg)
|
||||
raiseAssert(exc.msg)
|
||||
|
||||
chronicles.formatIt(LPChannel): shortLog(it)
|
||||
|
||||
proc open*(s: LPChannel) {.async, gcsafe.} =
|
||||
proc open*(s: LPChannel) {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
trace "Opening channel", s, conn = s.conn
|
||||
if s.conn.isClosed:
|
||||
return
|
||||
try:
|
||||
await s.conn.writeMsg(s.id, MessageType.New, s.name)
|
||||
s.isOpen = true
|
||||
except CatchableError as exc:
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except LPStreamError as exc:
|
||||
await s.conn.close()
|
||||
raise exc
|
||||
|
||||
method closed*(s: LPChannel): bool {.raises: [Defect].} =
|
||||
method closed*(s: LPChannel): bool =
|
||||
s.closedLocal
|
||||
|
||||
proc closeUnderlying(s: LPChannel): Future[void] {.async.} =
|
||||
proc closeUnderlying(s: LPChannel): Future[void] {.async: (raises: []).} =
|
||||
## Channels may be closed for reading and writing in any order - we'll close
|
||||
## the underlying bufferstream when both directions are closed
|
||||
if s.closedLocal and s.atEof():
|
||||
await procCall BufferStream(s).close()
|
||||
|
||||
proc reset*(s: LPChannel) {.async, gcsafe.} =
|
||||
proc reset*(s: LPChannel) {.async: (raises: []).} =
|
||||
if s.isClosed:
|
||||
trace "Already closed", s
|
||||
return
|
||||
|
||||
s.isClosed = true
|
||||
s.closedLocal = true
|
||||
s.localReset = not s.remoteReset
|
||||
|
||||
trace "Resetting channel", s, len = s.len
|
||||
|
||||
if s.isOpen and not s.conn.isClosed:
|
||||
# If the connection is still active, notify the other end
|
||||
proc resetMessage() {.async.} =
|
||||
proc resetMessage() {.async: (raises: []).} =
|
||||
try:
|
||||
trace "sending reset message", s, conn = s.conn
|
||||
await s.conn.writeMsg(s.id, s.resetCode) # write reset
|
||||
except CatchableError as exc:
|
||||
# No cancellations
|
||||
await s.conn.close()
|
||||
await noCancel s.conn.writeMsg(s.id, s.resetCode) # write reset
|
||||
except LPStreamError as exc:
|
||||
trace "Can't send reset message", s, conn = s.conn, msg = exc.msg
|
||||
await s.conn.close()
|
||||
|
||||
asyncSpawn resetMessage()
|
||||
|
||||
await s.closeImpl() # noraises, nocancels
|
||||
await s.closeImpl()
|
||||
|
||||
trace "Channel reset", s
|
||||
|
||||
method close*(s: LPChannel) {.async, gcsafe.} =
|
||||
method close*(s: LPChannel) {.async: (raises: []).} =
|
||||
## Close channel for writing - a message will be sent to the other peer
|
||||
## informing them that the channel is closed and that we're waiting for
|
||||
## their acknowledgement.
|
||||
@@ -132,10 +137,9 @@ method close*(s: LPChannel) {.async, gcsafe.} =
|
||||
if s.isOpen and not s.conn.isClosed:
|
||||
try:
|
||||
await s.conn.writeMsg(s.id, s.closeCode) # write close
|
||||
except CancelledError as exc:
|
||||
except CancelledError:
|
||||
await s.conn.close()
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
except LPStreamError as exc:
|
||||
# It's harmless that close message cannot be sent - the connection is
|
||||
# likely down already
|
||||
await s.conn.close()
|
||||
@@ -149,43 +153,60 @@ method initStream*(s: LPChannel) =
|
||||
if s.objName.len == 0:
|
||||
s.objName = LPChannelTrackerName
|
||||
|
||||
s.timeoutHandler = proc(): Future[void] {.gcsafe.} =
|
||||
s.timeoutHandler = proc(): Future[void] {.async: (raises: [], raw: true).} =
|
||||
trace "Idle timeout expired, resetting LPChannel", s
|
||||
s.reset()
|
||||
|
||||
procCall BufferStream(s).initStream()
|
||||
|
||||
method readOnce*(s: LPChannel,
|
||||
pbytes: pointer,
|
||||
nbytes: int):
|
||||
Future[int] {.async.} =
|
||||
method readOnce*(
|
||||
s: LPChannel,
|
||||
pbytes: pointer,
|
||||
nbytes: int
|
||||
): Future[int] {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
## Mplex relies on reading being done regularly from every channel, or all
|
||||
## channels are blocked - in particular, this means that reading from one
|
||||
## channel must not be done from within a callback / read handler of another
|
||||
## or the reads will lock each other.
|
||||
if s.remoteReset:
|
||||
raise newLPStreamResetError()
|
||||
if s.localReset:
|
||||
raise newLPStreamClosedError()
|
||||
if s.atEof():
|
||||
raise newLPStreamRemoteClosedError()
|
||||
if s.conn.closed:
|
||||
raise newLPStreamConnDownError()
|
||||
try:
|
||||
let bytes = await procCall BufferStream(s).readOnce(pbytes, nbytes)
|
||||
when defined(libp2p_network_protocols_metrics):
|
||||
if s.tag.len > 0:
|
||||
libp2p_protocols_bytes.inc(bytes.int64, labelValues=[s.tag, "in"])
|
||||
if s.protocol.len > 0:
|
||||
libp2p_protocols_bytes.inc(bytes.int64, labelValues=[s.protocol, "in"])
|
||||
|
||||
trace "readOnce", s, bytes
|
||||
if bytes == 0:
|
||||
await s.closeUnderlying()
|
||||
return bytes
|
||||
except CatchableError as exc:
|
||||
# readOnce in BufferStream generally raises on EOF or cancellation - for
|
||||
# the former, resetting is harmless, for the latter it's necessary because
|
||||
# data has been lost in s.readBuf and there's no way to gracefully recover /
|
||||
# use the channel any more
|
||||
except CancelledError as exc:
|
||||
await s.reset()
|
||||
raise exc
|
||||
except LPStreamError as exc:
|
||||
# Resetting is necessary because data has been lost in s.readBuf and
|
||||
# there's no way to gracefully recover / use the channel any more
|
||||
await s.reset()
|
||||
raise newLPStreamConnDownError(exc)
|
||||
|
||||
proc prepareWrite(s: LPChannel, msg: seq[byte]): Future[void] {.async.} =
|
||||
proc prepareWrite(
|
||||
s: LPChannel,
|
||||
msg: seq[byte]
|
||||
): Future[void] {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
# prepareWrite is the slow path of writing a message - see conditions in
|
||||
# write
|
||||
if s.closedLocal or s.conn.closed:
|
||||
if s.remoteReset:
|
||||
raise newLPStreamResetError()
|
||||
if s.closedLocal:
|
||||
raise newLPStreamClosedError()
|
||||
if s.conn.closed:
|
||||
raise newLPStreamConnDownError()
|
||||
|
||||
if msg.len == 0:
|
||||
return
|
||||
@@ -205,7 +226,10 @@ proc prepareWrite(s: LPChannel, msg: seq[byte]): Future[void] {.async.} =
|
||||
await s.conn.writeMsg(s.id, s.msgCode, msg)
|
||||
|
||||
proc completeWrite(
|
||||
s: LPChannel, fut: Future[void], msgLen: int): Future[void] {.async.} =
|
||||
s: LPChannel,
|
||||
fut: Future[void].Raising([CancelledError, LPStreamError]),
|
||||
msgLen: int
|
||||
): Future[void] {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
try:
|
||||
s.writes += 1
|
||||
|
||||
@@ -216,20 +240,36 @@ proc completeWrite(
|
||||
else:
|
||||
await fut
|
||||
|
||||
when defined(libp2p_network_protocol_metrics):
|
||||
if s.tag.len > 0:
|
||||
libp2p_protocols_bytes.inc(msgLen.int64, labelValues=[s.tag, "out"])
|
||||
when defined(libp2p_network_protocols_metrics):
|
||||
if s.protocol.len > 0:
|
||||
# This crashes on Nim 2.0.2 with `--mm:orc` during `nimble test`
|
||||
# https://github.com/status-im/nim-metrics/issues/79
|
||||
libp2p_protocols_bytes.inc(
|
||||
msgLen.int64, labelValues = [s.protocol, "out"])
|
||||
|
||||
s.activity = true
|
||||
except CatchableError as exc:
|
||||
trace "exception in lpchannel write handler", s, msg = exc.msg
|
||||
except CancelledError as exc:
|
||||
# Chronos may still send the data
|
||||
raise exc
|
||||
except LPStreamConnDownError as exc:
|
||||
await s.reset()
|
||||
await s.conn.close()
|
||||
raise exc
|
||||
except LPStreamEOFError as exc:
|
||||
raise exc
|
||||
except LPStreamError as exc:
|
||||
trace "exception in lpchannel write handler", s, msg = exc.msg
|
||||
await s.reset()
|
||||
await s.conn.close()
|
||||
raise newLPStreamConnDownError(exc)
|
||||
finally:
|
||||
s.writes -= 1
|
||||
|
||||
method write*(s: LPChannel, msg: seq[byte]): Future[void] =
|
||||
method write*(
|
||||
s: LPChannel,
|
||||
msg: seq[byte]
|
||||
): Future[void] {.async: (raises: [
|
||||
CancelledError, LPStreamError], raw: true).} =
|
||||
## Write to mplex channel - there may be up to MaxWrite concurrent writes
|
||||
## pending after which the peer is disconnected
|
||||
|
||||
@@ -247,14 +287,15 @@ method write*(s: LPChannel, msg: seq[byte]): Future[void] =
|
||||
|
||||
s.completeWrite(fut, msg.len)
|
||||
|
||||
proc init*(
|
||||
L: type LPChannel,
|
||||
id: uint64,
|
||||
conn: Connection,
|
||||
initiator: bool,
|
||||
name: string = "",
|
||||
timeout: Duration = DefaultChanTimeout): LPChannel =
|
||||
method getWrapped*(s: LPChannel): Connection = s.conn
|
||||
|
||||
proc init*(
|
||||
L: type LPChannel,
|
||||
id: uint64,
|
||||
conn: Connection,
|
||||
initiator: bool,
|
||||
name: string = "",
|
||||
timeout: Duration = DefaultChanTimeout): LPChannel =
|
||||
let chann = L(
|
||||
id: id,
|
||||
name: name,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2019 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import tables, sequtils, oids
|
||||
import chronos, chronicles, stew/byteutils, metrics
|
||||
@@ -34,7 +34,6 @@ when defined(libp2p_expensive_metrics):
|
||||
"mplex channels", labels = ["initiator", "peer"])
|
||||
|
||||
type
|
||||
TooManyChannels* = object of MuxerError
|
||||
InvalidChannelIdError* = object of MuxerError
|
||||
|
||||
Mplex* = ref object of Muxer
|
||||
@@ -57,7 +56,7 @@ proc newTooManyChannels(): ref TooManyChannels =
|
||||
proc newInvalidChannelIdError(): ref InvalidChannelIdError =
|
||||
newException(InvalidChannelIdError, "max allowed channel count exceeded")
|
||||
|
||||
proc cleanupChann(m: Mplex, chann: LPChannel) {.async, inline.} =
|
||||
proc cleanupChann(m: Mplex, chann: LPChannel) {.async: (raises: []), inline.} =
|
||||
## remove the local channel from the internal tables
|
||||
##
|
||||
try:
|
||||
@@ -69,19 +68,19 @@ proc cleanupChann(m: Mplex, chann: LPChannel) {.async, inline.} =
|
||||
libp2p_mplex_channels.set(
|
||||
m.channels[chann.initiator].len.int64,
|
||||
labelValues = [$chann.initiator, $m.connection.peerId])
|
||||
except CatchableError as exc:
|
||||
except CancelledError as exc:
|
||||
warn "Error cleaning up mplex channel", m, chann, msg = exc.msg
|
||||
|
||||
proc newStreamInternal*(m: Mplex,
|
||||
initiator: bool = true,
|
||||
chanId: uint64 = 0,
|
||||
name: string = "",
|
||||
timeout: Duration): LPChannel
|
||||
{.gcsafe, raises: [Defect, InvalidChannelIdError].} =
|
||||
proc newStreamInternal*(
|
||||
m: Mplex,
|
||||
initiator: bool = true,
|
||||
chanId: uint64 = 0,
|
||||
name: string = "",
|
||||
timeout: Duration): LPChannel {.gcsafe, raises: [InvalidChannelIdError].} =
|
||||
## create new channel/stream
|
||||
##
|
||||
let id = if initiator:
|
||||
m.currentId.inc(); m.currentId
|
||||
let id =
|
||||
if initiator: m.currentId.inc(); m.currentId
|
||||
else: chanId
|
||||
|
||||
if id in m.channels[initiator]:
|
||||
@@ -112,18 +111,14 @@ proc newStreamInternal*(m: Mplex,
|
||||
m.channels[initiator].len.int64,
|
||||
labelValues = [$initiator, $m.connection.peerId])
|
||||
|
||||
proc handleStream(m: Mplex, chann: LPChannel) {.async.} =
|
||||
proc handleStream(m: Mplex, chann: LPChannel) {.async: (raises: []).} =
|
||||
## call the muxer stream handler for this channel
|
||||
##
|
||||
try:
|
||||
await m.streamHandler(chann)
|
||||
trace "finished handling stream", m, chann
|
||||
doAssert(chann.closed, "connection not closed by handler!")
|
||||
except CatchableError as exc:
|
||||
trace "Exception in mplex stream handler", m, chann, msg = exc.msg
|
||||
await chann.reset()
|
||||
await m.streamHandler(chann)
|
||||
trace "finished handling stream", m, chann
|
||||
doAssert(chann.closed, "connection not closed by handler!")
|
||||
|
||||
method handle*(m: Mplex) {.async, gcsafe.} =
|
||||
method handle*(m: Mplex) {.async: (raises: []).} =
|
||||
trace "Starting mplex handler", m
|
||||
try:
|
||||
while not m.connection.atEof:
|
||||
@@ -151,7 +146,7 @@ method handle*(m: Mplex) {.async, gcsafe.} =
|
||||
else:
|
||||
if m.channels[false].len > m.maxChannCount - 1:
|
||||
warn "too many channels created by remote peer",
|
||||
allowedMax = MaxChannelCount, m
|
||||
allowedMax = MaxChannelCount, m
|
||||
raise newTooManyChannels()
|
||||
|
||||
let name = string.fromBytes(data)
|
||||
@@ -160,51 +155,65 @@ method handle*(m: Mplex) {.async, gcsafe.} =
|
||||
trace "Processing channel message", m, channel, data = data.shortLog
|
||||
|
||||
case msgType:
|
||||
of MessageType.New:
|
||||
trace "created channel", m, channel
|
||||
of MessageType.New:
|
||||
trace "created channel", m, channel
|
||||
|
||||
if not isNil(m.streamHandler):
|
||||
# Launch handler task
|
||||
# All the errors are handled inside `handleStream()` procedure.
|
||||
asyncSpawn m.handleStream(channel)
|
||||
if m.streamHandler != nil:
|
||||
# Launch handler task
|
||||
# All the errors are handled inside `handleStream()` procedure.
|
||||
asyncSpawn m.handleStream(channel)
|
||||
|
||||
of MessageType.MsgIn, MessageType.MsgOut:
|
||||
if data.len > MaxMsgSize:
|
||||
warn "attempting to send a packet larger than allowed",
|
||||
allowed = MaxMsgSize, channel
|
||||
raise newLPStreamLimitError()
|
||||
of MessageType.MsgIn, MessageType.MsgOut:
|
||||
if data.len > MaxMsgSize:
|
||||
warn "attempting to send a packet larger than allowed",
|
||||
allowed = MaxMsgSize, channel
|
||||
raise newLPStreamLimitError()
|
||||
|
||||
trace "pushing data to channel", m, channel, len = data.len
|
||||
trace "pushing data to channel", m, channel, len = data.len
|
||||
try:
|
||||
await channel.pushData(data)
|
||||
trace "pushed data to channel", m, channel, len = data.len
|
||||
except LPStreamClosedError as exc:
|
||||
# Channel is being closed, but `cleanupChann` was not yet triggered.
|
||||
trace "pushing data to channel failed", m, channel, len = data.len,
|
||||
msg = exc.msg
|
||||
discard # Ignore message, same as if `cleanupChann` had completed.
|
||||
|
||||
of MessageType.CloseIn, MessageType.CloseOut:
|
||||
await channel.pushEof()
|
||||
of MessageType.ResetIn, MessageType.ResetOut:
|
||||
await channel.reset()
|
||||
of MessageType.CloseIn, MessageType.CloseOut:
|
||||
await channel.pushEof()
|
||||
of MessageType.ResetIn, MessageType.ResetOut:
|
||||
channel.remoteReset = true
|
||||
await channel.reset()
|
||||
except CancelledError:
|
||||
debug "Unexpected cancellation in mplex handler", m
|
||||
except LPStreamEOFError as exc:
|
||||
trace "Stream EOF", m, msg = exc.msg
|
||||
except CatchableError as exc:
|
||||
debug "Unexpected exception in mplex read loop", m, msg = exc.msg
|
||||
except LPStreamError as exc:
|
||||
debug "Unexpected stream exception in mplex read loop", m, msg = exc.msg
|
||||
except MuxerError as exc:
|
||||
debug "Unexpected muxer exception in mplex read loop", m, msg = exc.msg
|
||||
finally:
|
||||
await m.close()
|
||||
trace "Stopped mplex handler", m
|
||||
|
||||
proc new*(M: type Mplex,
|
||||
conn: Connection,
|
||||
inTimeout, outTimeout: Duration = DefaultChanTimeout,
|
||||
maxChannCount: int = MaxChannelCount): Mplex =
|
||||
proc new*(
|
||||
M: type Mplex,
|
||||
conn: Connection,
|
||||
inTimeout: Duration = DefaultChanTimeout,
|
||||
outTimeout: Duration = DefaultChanTimeout,
|
||||
maxChannCount: int = MaxChannelCount): Mplex =
|
||||
M(connection: conn,
|
||||
inChannTimeout: inTimeout,
|
||||
outChannTimeout: outTimeout,
|
||||
oid: genOid(),
|
||||
maxChannCount: maxChannCount)
|
||||
|
||||
method newStream*(m: Mplex,
|
||||
name: string = "",
|
||||
lazy: bool = false): Future[Connection] {.async, gcsafe.} =
|
||||
method newStream*(
|
||||
m: Mplex,
|
||||
name: string = "",
|
||||
lazy: bool = false
|
||||
): Future[Connection] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MuxerError]).} =
|
||||
let channel = m.newStreamInternal(timeout = m.inChannTimeout)
|
||||
|
||||
if not lazy:
|
||||
@@ -212,7 +221,7 @@ method newStream*(m: Mplex,
|
||||
|
||||
return Connection(channel)
|
||||
|
||||
method close*(m: Mplex) {.async, gcsafe.} =
|
||||
method close*(m: Mplex) {.async: (raises: []).} =
|
||||
if m.isClosed:
|
||||
trace "Already closed", m
|
||||
return
|
||||
@@ -239,3 +248,7 @@ method close*(m: Mplex) {.async, gcsafe.} =
|
||||
m.channels[true].clear()
|
||||
|
||||
trace "Closed mplex", m
|
||||
|
||||
method getStreams*(m: Mplex): seq[Connection] =
|
||||
for c in m.channels[false].values: result.add(c)
|
||||
for c in m.channels[true].values: result.add(c)
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2019 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import chronos, chronicles
|
||||
import ../protocols/protocol,
|
||||
../stream/connection,
|
||||
import ../stream/connection,
|
||||
../errors
|
||||
|
||||
logScope:
|
||||
@@ -22,67 +21,52 @@ const
|
||||
|
||||
type
|
||||
MuxerError* = object of LPError
|
||||
TooManyChannels* = object of MuxerError
|
||||
|
||||
StreamHandler* = proc(conn: Connection): Future[void] {.gcsafe, raises: [Defect].}
|
||||
MuxerHandler* = proc(muxer: Muxer): Future[void] {.gcsafe, raises: [Defect].}
|
||||
StreamHandler* = proc(conn: Connection): Future[void] {.async: (raises: []).}
|
||||
MuxerHandler* = proc(muxer: Muxer): Future[void] {.async: (raises: []).}
|
||||
|
||||
Muxer* = ref object of RootObj
|
||||
streamHandler*: StreamHandler
|
||||
handler*: Future[void].Raising([])
|
||||
connection*: Connection
|
||||
|
||||
# user provider proc that returns a constructed Muxer
|
||||
MuxerConstructor* = proc(conn: Connection): Muxer {.gcsafe, closure, raises: [Defect].}
|
||||
MuxerConstructor* =
|
||||
proc(conn: Connection): Muxer {.gcsafe, closure, raises: [].}
|
||||
|
||||
# this wraps a creator proc that knows how to make muxers
|
||||
MuxerProvider* = ref object of LPProtocol
|
||||
MuxerProvider* = object
|
||||
newMuxer*: MuxerConstructor
|
||||
streamHandler*: StreamHandler # triggered every time there is a new stream, called for any muxer instance
|
||||
muxerHandler*: MuxerHandler # triggered every time there is a new muxed connection created
|
||||
codec*: string
|
||||
|
||||
func shortLog*(m: Muxer): auto =
|
||||
if m == nil: "nil"
|
||||
else: shortLog(m.connection)
|
||||
|
||||
func shortLog*(m: Muxer): auto = shortLog(m.connection)
|
||||
chronicles.formatIt(Muxer): shortLog(it)
|
||||
|
||||
# muxer interface
|
||||
method newStream*(m: Muxer, name: string = "", lazy: bool = false):
|
||||
Future[Connection] {.base, async, gcsafe.} = discard
|
||||
method close*(m: Muxer) {.base, async, gcsafe.} = discard
|
||||
method handle*(m: Muxer): Future[void] {.base, async, gcsafe.} = discard
|
||||
method newStream*(
|
||||
m: Muxer,
|
||||
name: string = "",
|
||||
lazy: bool = false
|
||||
): Future[Connection] {.base, async: (raises: [
|
||||
CancelledError, LPStreamError, MuxerError], raw: true).} =
|
||||
raiseAssert("Not implemented!")
|
||||
|
||||
method close*(m: Muxer) {.base, async: (raises: []).} =
|
||||
if m.connection != nil:
|
||||
await m.connection.close()
|
||||
|
||||
method handle*(m: Muxer): Future[void] {.base, async: (raises: []).} = discard
|
||||
|
||||
proc new*(
|
||||
T: typedesc[MuxerProvider],
|
||||
creator: MuxerConstructor,
|
||||
codec: string): T {.gcsafe.} =
|
||||
|
||||
let muxerProvider = T(newMuxer: creator)
|
||||
muxerProvider.codec = codec
|
||||
muxerProvider.init()
|
||||
T: typedesc[MuxerProvider],
|
||||
creator: MuxerConstructor,
|
||||
codec: string): T {.gcsafe.} =
|
||||
let muxerProvider = T(newMuxer: creator, codec: codec)
|
||||
muxerProvider
|
||||
|
||||
method init(c: MuxerProvider) =
|
||||
proc handler(conn: Connection, proto: string) {.async, gcsafe, closure.} =
|
||||
trace "starting muxer handler", proto=proto, conn
|
||||
try:
|
||||
let
|
||||
muxer = c.newMuxer(conn)
|
||||
|
||||
if not isNil(c.streamHandler):
|
||||
muxer.streamHandler = c.streamHandler
|
||||
|
||||
var futs = newSeq[Future[void]]()
|
||||
futs &= muxer.handle()
|
||||
|
||||
# finally await both the futures
|
||||
if not isNil(c.muxerHandler):
|
||||
await c.muxerHandler(muxer)
|
||||
when defined(libp2p_agents_metrics):
|
||||
conn.shortAgent = muxer.connection.shortAgent
|
||||
|
||||
checkFutures(await allFinished(futs))
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
trace "exception in muxer handler", exc = exc.msg, conn, proto
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
c.handler = handler
|
||||
method getStreams*(m: Muxer): seq[Connection] {.base.} =
|
||||
raiseAssert("Not implemented!")
|
||||
|
||||
646
libp2p/muxers/yamux/yamux.nim
Normal file
646
libp2p/muxers/yamux/yamux.nim
Normal file
@@ -0,0 +1,646 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import sequtils, std/[tables]
|
||||
import chronos, chronicles, metrics, stew/[endians2, byteutils, objects]
|
||||
import ../muxer,
|
||||
../../stream/connection
|
||||
|
||||
export muxer
|
||||
|
||||
logScope:
|
||||
topics = "libp2p yamux"
|
||||
|
||||
const
|
||||
YamuxCodec* = "/yamux/1.0.0"
|
||||
YamuxVersion = 0.uint8
|
||||
YamuxDefaultWindowSize* = 256000
|
||||
MaxSendQueueSize = 256000
|
||||
MaxChannelCount = 200
|
||||
|
||||
when defined(libp2p_yamux_metrics):
|
||||
declareGauge libp2p_yamux_channels,
|
||||
"yamux channels", labels = ["initiator", "peer"]
|
||||
declareHistogram libp2p_yamux_send_queue,
|
||||
"message send queue length (in byte)", buckets = [
|
||||
0.0, 100.0, 250.0, 1000.0, 2000.0, 3200.0, 6400.0, 25600.0, 256000.0]
|
||||
declareHistogram libp2p_yamux_recv_queue,
|
||||
"message recv queue length (in byte)", buckets = [
|
||||
0.0, 100.0, 250.0, 1000.0, 2000.0, 3200.0, 6400.0, 25600.0, 256000.0]
|
||||
|
||||
type
|
||||
YamuxError* = object of MuxerError
|
||||
|
||||
MsgType = enum
|
||||
Data = 0x0
|
||||
WindowUpdate = 0x1
|
||||
Ping = 0x2
|
||||
GoAway = 0x3
|
||||
|
||||
MsgFlags {.size: 2.} = enum
|
||||
Syn
|
||||
Ack
|
||||
Fin
|
||||
Rst
|
||||
|
||||
GoAwayStatus = enum
|
||||
NormalTermination = 0x0,
|
||||
ProtocolError = 0x1,
|
||||
InternalError = 0x2,
|
||||
|
||||
YamuxHeader = object
|
||||
version: uint8
|
||||
msgType: MsgType
|
||||
flags: set[MsgFlags]
|
||||
streamId: uint32
|
||||
length: uint32
|
||||
|
||||
proc readHeader(
|
||||
conn: LPStream
|
||||
): Future[YamuxHeader] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MuxerError]).} =
|
||||
var buffer: array[12, byte]
|
||||
await conn.readExactly(addr buffer[0], 12)
|
||||
|
||||
result.version = buffer[0]
|
||||
let flags = fromBytesBE(uint16, buffer[2..3])
|
||||
if not result.msgType.checkedEnumAssign(buffer[1]) or flags notin 0'u16..15'u16:
|
||||
raise newException(YamuxError, "Wrong header")
|
||||
result.flags = cast[set[MsgFlags]](flags)
|
||||
result.streamId = fromBytesBE(uint32, buffer[4..7])
|
||||
result.length = fromBytesBE(uint32, buffer[8..11])
|
||||
return result
|
||||
|
||||
proc `$`(header: YamuxHeader): string =
|
||||
"{" & $header.msgType & ", " &
|
||||
"{" & header.flags.foldl(if a != "": a & ", " & $b else: $b, "") & "}, " &
|
||||
"streamId: " & $header.streamId & ", " &
|
||||
"length: " & $header.length & "}"
|
||||
|
||||
proc encode(header: YamuxHeader): array[12, byte] =
|
||||
result[0] = header.version
|
||||
result[1] = uint8(header.msgType)
|
||||
result[2..3] = toBytesBE(uint16(cast[uint8](header.flags))) # workaround https://github.com/nim-lang/Nim/issues/21789
|
||||
result[4..7] = toBytesBE(header.streamId)
|
||||
result[8..11] = toBytesBE(header.length)
|
||||
|
||||
proc write(
|
||||
conn: LPStream,
|
||||
header: YamuxHeader
|
||||
): Future[void] {.async: (raises: [
|
||||
CancelledError, LPStreamError], raw: true).} =
|
||||
trace "write directly on stream", h = $header
|
||||
var buffer = header.encode()
|
||||
conn.write(@buffer)
|
||||
|
||||
proc ping(T: type[YamuxHeader], flag: MsgFlags, pingData: uint32): T =
|
||||
T(
|
||||
version: YamuxVersion,
|
||||
msgType: MsgType.Ping,
|
||||
flags: {flag},
|
||||
length: pingData
|
||||
)
|
||||
|
||||
proc goAway(T: type[YamuxHeader], status: GoAwayStatus): T =
|
||||
T(
|
||||
version: YamuxVersion,
|
||||
msgType: MsgType.GoAway,
|
||||
length: uint32(status)
|
||||
)
|
||||
|
||||
proc data(
|
||||
T: type[YamuxHeader],
|
||||
streamId: uint32,
|
||||
length: uint32 = 0,
|
||||
flags: set[MsgFlags] = {}): T =
|
||||
T(
|
||||
version: YamuxVersion,
|
||||
msgType: MsgType.Data,
|
||||
length: length,
|
||||
flags: flags,
|
||||
streamId: streamId
|
||||
)
|
||||
|
||||
proc windowUpdate(
|
||||
T: type[YamuxHeader],
|
||||
streamId: uint32,
|
||||
delta: uint32,
|
||||
flags: set[MsgFlags] = {}): T =
|
||||
T(
|
||||
version: YamuxVersion,
|
||||
msgType: MsgType.WindowUpdate,
|
||||
length: delta,
|
||||
flags: flags,
|
||||
streamId: streamId
|
||||
)
|
||||
|
||||
type
|
||||
ToSend = tuple
|
||||
data: seq[byte]
|
||||
sent: int
|
||||
fut: Future[void].Raising([CancelledError, LPStreamError])
|
||||
YamuxChannel* = ref object of Connection
|
||||
id: uint32
|
||||
recvWindow: int
|
||||
sendWindow: int
|
||||
maxRecvWindow: int
|
||||
maxSendQueueSize: int
|
||||
conn: Connection
|
||||
isSrc: bool
|
||||
opened: bool
|
||||
isSending: bool
|
||||
sendQueue: seq[ToSend]
|
||||
recvQueue: seq[byte]
|
||||
isReset: bool
|
||||
remoteReset: bool
|
||||
closedRemotely: Future[void].Raising([])
|
||||
closedLocally: bool
|
||||
receivedData: AsyncEvent
|
||||
|
||||
proc `$`(channel: YamuxChannel): string =
|
||||
result = if channel.conn.dir == Out: "=> " else: "<= "
|
||||
result &= $channel.id
|
||||
var s: seq[string] = @[]
|
||||
if channel.closedRemotely.completed():
|
||||
s.add("ClosedRemotely")
|
||||
if channel.closedLocally:
|
||||
s.add("ClosedLocally")
|
||||
if channel.isReset:
|
||||
s.add("Reset")
|
||||
if s.len > 0:
|
||||
result &= " {" & s.foldl(if a != "": a & ", " & b else: b, "") & "}"
|
||||
|
||||
proc lengthSendQueue(channel: YamuxChannel): int =
|
||||
## Returns the length of what remains to be sent
|
||||
##
|
||||
channel.sendQueue.foldl(a + b.data.len - b.sent, 0)
|
||||
|
||||
proc lengthSendQueueWithLimit(channel: YamuxChannel): int =
|
||||
## Returns the length of what remains to be sent, but limit the size of big messages.
|
||||
##
|
||||
# For leniency, limit big messages size to the third of maxSendQueueSize
|
||||
# This value is arbitrary, it's not in the specs, it permits to store up to
|
||||
# 3 big messages if the peer is stalling.
|
||||
channel.sendQueue.foldl(a + min(b.data.len - b.sent, channel.maxSendQueueSize div 3), 0)
|
||||
|
||||
proc actuallyClose(channel: YamuxChannel) {.async: (raises: []).} =
|
||||
if channel.closedLocally and channel.sendQueue.len == 0 and
|
||||
channel.closedRemotely.completed():
|
||||
await procCall Connection(channel).closeImpl()
|
||||
|
||||
proc remoteClosed(channel: YamuxChannel) {.async: (raises: []).} =
|
||||
if not channel.closedRemotely.completed():
|
||||
channel.closedRemotely.complete()
|
||||
await channel.actuallyClose()
|
||||
|
||||
method closeImpl*(channel: YamuxChannel) {.async: (raises: []).} =
|
||||
if not channel.closedLocally:
|
||||
trace "Closing yamux channel locally", streamId = channel.id, conn = channel.conn
|
||||
channel.closedLocally = true
|
||||
|
||||
if not channel.isReset and channel.sendQueue.len == 0:
|
||||
try: await channel.conn.write(YamuxHeader.data(channel.id, 0, {Fin}))
|
||||
except CancelledError, LPStreamError: discard
|
||||
await channel.actuallyClose()
|
||||
|
||||
proc reset(
|
||||
channel: YamuxChannel, isLocal: bool = false) {.async: (raises: []).} =
|
||||
# If we reset locally, we want to flush up to a maximum of recvWindow
|
||||
# bytes. It's because the peer we're connected to can send us data before
|
||||
# it receives the reset.
|
||||
if channel.isReset:
|
||||
return
|
||||
trace "Reset channel"
|
||||
channel.isReset = true
|
||||
channel.remoteReset = not isLocal
|
||||
for (d, s, fut) in channel.sendQueue:
|
||||
fut.fail(newLPStreamEOFError())
|
||||
channel.sendQueue = @[]
|
||||
channel.recvQueue = @[]
|
||||
channel.sendWindow = 0
|
||||
if not channel.closedLocally:
|
||||
if isLocal and not channel.isSending:
|
||||
try: await channel.conn.write(YamuxHeader.data(channel.id, 0, {Rst}))
|
||||
except CancelledError, LPStreamError: discard
|
||||
await channel.close()
|
||||
if not channel.closedRemotely.completed():
|
||||
await channel.remoteClosed()
|
||||
channel.receivedData.fire()
|
||||
if not isLocal:
|
||||
# If the reset is remote, there's no reason to flush anything.
|
||||
channel.recvWindow = 0
|
||||
|
||||
proc updateRecvWindow(
|
||||
channel: YamuxChannel
|
||||
) {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
## Send to the peer a window update when the recvWindow is empty enough
|
||||
##
|
||||
# In order to avoid spamming a window update everytime a byte is read,
|
||||
# we send it everytime half of the maxRecvWindow is read.
|
||||
let inWindow = channel.recvWindow + channel.recvQueue.len
|
||||
if inWindow > channel.maxRecvWindow div 2:
|
||||
return
|
||||
|
||||
let delta = channel.maxRecvWindow - inWindow
|
||||
channel.recvWindow.inc(delta)
|
||||
await channel.conn.write(YamuxHeader.windowUpdate(
|
||||
channel.id,
|
||||
delta.uint32
|
||||
))
|
||||
trace "increasing the recvWindow", delta
|
||||
|
||||
method readOnce*(
|
||||
channel: YamuxChannel,
|
||||
pbytes: pointer,
|
||||
nbytes: int
|
||||
): Future[int] {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
## Read from a yamux channel
|
||||
|
||||
if channel.isReset:
|
||||
raise
|
||||
if channel.remoteReset:
|
||||
newLPStreamResetError()
|
||||
elif channel.closedLocally:
|
||||
newLPStreamClosedError()
|
||||
else:
|
||||
newLPStreamConnDownError()
|
||||
if channel.isEof:
|
||||
raise newLPStreamRemoteClosedError()
|
||||
if channel.recvQueue.len == 0:
|
||||
channel.receivedData.clear()
|
||||
try: # https://github.com/status-im/nim-chronos/issues/516
|
||||
discard await race(channel.closedRemotely, channel.receivedData.wait())
|
||||
except ValueError: raiseAssert("Futures list is not empty")
|
||||
if channel.closedRemotely.completed() and channel.recvQueue.len == 0:
|
||||
channel.isEof = true
|
||||
return 0 # we return 0 to indicate that the channel is closed for reading from now on
|
||||
|
||||
let toRead = min(channel.recvQueue.len, nbytes)
|
||||
|
||||
var p = cast[ptr UncheckedArray[byte]](pbytes)
|
||||
toOpenArray(p, 0, nbytes - 1)[0..<toRead] =
|
||||
channel.recvQueue.toOpenArray(0, toRead - 1)
|
||||
channel.recvQueue = channel.recvQueue[toRead..^1]
|
||||
|
||||
# We made some room in the recv buffer let the peer know
|
||||
await channel.updateRecvWindow()
|
||||
channel.activity = true
|
||||
return toRead
|
||||
|
||||
proc gotDataFromRemote(
|
||||
channel: YamuxChannel,
|
||||
b: seq[byte]) {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
channel.recvWindow -= b.len
|
||||
channel.recvQueue = channel.recvQueue.concat(b)
|
||||
channel.receivedData.fire()
|
||||
when defined(libp2p_yamux_metrics):
|
||||
libp2p_yamux_recv_queue.observe(channel.recvQueue.len.int64)
|
||||
await channel.updateRecvWindow()
|
||||
|
||||
proc setMaxRecvWindow*(channel: YamuxChannel, maxRecvWindow: int) =
|
||||
channel.maxRecvWindow = maxRecvWindow
|
||||
|
||||
proc trySend(
|
||||
channel: YamuxChannel
|
||||
) {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
if channel.isSending:
|
||||
return
|
||||
channel.isSending = true
|
||||
defer: channel.isSending = false
|
||||
|
||||
while channel.sendQueue.len != 0:
|
||||
channel.sendQueue.keepItIf(not (it.fut.cancelled() and it.sent == 0))
|
||||
if channel.sendWindow == 0:
|
||||
trace "trying to send while the sendWindow is empty"
|
||||
if channel.lengthSendQueueWithLimit() > channel.maxSendQueueSize:
|
||||
trace "channel send queue too big, resetting",
|
||||
maxSendQueueSize = channel.maxSendQueueSize,
|
||||
currentQueueSize = channel.lengthSendQueueWithLimit()
|
||||
await channel.reset(isLocal = true)
|
||||
break
|
||||
|
||||
let
|
||||
bytesAvailable = channel.lengthSendQueue()
|
||||
toSend = min(channel.sendWindow, bytesAvailable)
|
||||
var
|
||||
sendBuffer = newSeqUninitialized[byte](toSend + 12)
|
||||
header = YamuxHeader.data(channel.id, toSend.uint32)
|
||||
inBuffer = 0
|
||||
|
||||
if toSend >= bytesAvailable and channel.closedLocally:
|
||||
trace "last buffer we'll sent on this channel", toSend, bytesAvailable
|
||||
header.flags.incl({Fin})
|
||||
|
||||
sendBuffer[0..<12] = header.encode()
|
||||
|
||||
var futures: seq[Future[void].Raising([CancelledError, LPStreamError])]
|
||||
while inBuffer < toSend:
|
||||
# concatenate the different message we try to send into one buffer
|
||||
let (data, sent, fut) = channel.sendQueue[0]
|
||||
let bufferToSend = min(data.len - sent, toSend - inBuffer)
|
||||
sendBuffer.toOpenArray(12, 12 + toSend - 1)[inBuffer..<(inBuffer+bufferToSend)] =
|
||||
channel.sendQueue[0].data.toOpenArray(sent, sent + bufferToSend - 1)
|
||||
channel.sendQueue[0].sent.inc(bufferToSend)
|
||||
if channel.sendQueue[0].sent >= data.len:
|
||||
# if every byte of the message is in the buffer, add the write future to the
|
||||
# sequence of futures to be completed (or failed) when the buffer is sent
|
||||
futures.add(fut)
|
||||
channel.sendQueue.delete(0)
|
||||
inBuffer.inc(bufferToSend)
|
||||
|
||||
trace "try to send the buffer", h = $header
|
||||
channel.sendWindow.dec(toSend)
|
||||
try:
|
||||
await channel.conn.write(sendBuffer)
|
||||
except CancelledError:
|
||||
trace "cancelled sending the buffer"
|
||||
for fut in futures.items():
|
||||
fut.cancelSoon()
|
||||
await channel.reset()
|
||||
break
|
||||
except LPStreamError as exc:
|
||||
trace "failed to send the buffer"
|
||||
let connDown = newLPStreamConnDownError(exc)
|
||||
for fut in futures.items():
|
||||
fut.fail(connDown)
|
||||
await channel.reset()
|
||||
break
|
||||
for fut in futures.items():
|
||||
fut.complete()
|
||||
channel.activity = true
|
||||
|
||||
method write*(
|
||||
channel: YamuxChannel,
|
||||
msg: seq[byte]
|
||||
): Future[void] {.async: (raises: [
|
||||
CancelledError, LPStreamError], raw: true).} =
|
||||
## Write to yamux channel
|
||||
##
|
||||
result = newFuture[void]("Yamux Send")
|
||||
if channel.remoteReset:
|
||||
result.fail(newLPStreamResetError())
|
||||
return result
|
||||
if channel.closedLocally or channel.isReset:
|
||||
result.fail(newLPStreamClosedError())
|
||||
return result
|
||||
if msg.len == 0:
|
||||
result.complete()
|
||||
return result
|
||||
channel.sendQueue.add((msg, 0, result))
|
||||
when defined(libp2p_yamux_metrics):
|
||||
libp2p_yamux_send_queue.observe(channel.lengthSendQueue().int64)
|
||||
asyncSpawn channel.trySend()
|
||||
|
||||
proc open(
|
||||
channel: YamuxChannel
|
||||
) {.async: (raises: [CancelledError, LPStreamError]).} =
|
||||
## Open a yamux channel by sending a window update with Syn or Ack flag
|
||||
##
|
||||
if channel.opened:
|
||||
trace "Try to open channel twice"
|
||||
return
|
||||
channel.opened = true
|
||||
await channel.conn.write(YamuxHeader.windowUpdate(
|
||||
channel.id,
|
||||
uint32(max(channel.maxRecvWindow - YamuxDefaultWindowSize, 0)),
|
||||
{if channel.isSrc: Syn else: Ack}))
|
||||
|
||||
method getWrapped*(channel: YamuxChannel): Connection = channel.conn
|
||||
|
||||
type
|
||||
Yamux* = ref object of Muxer
|
||||
channels: Table[uint32, YamuxChannel]
|
||||
flushed: Table[uint32, int]
|
||||
currentId: uint32
|
||||
isClosed: bool
|
||||
maxChannCount: int
|
||||
windowSize: int
|
||||
maxSendQueueSize: int
|
||||
inTimeout: Duration
|
||||
outTimeout: Duration
|
||||
|
||||
proc lenBySrc(m: Yamux, isSrc: bool): int =
|
||||
for v in m.channels.values():
|
||||
if v.isSrc == isSrc: result += 1
|
||||
|
||||
proc cleanupChannel(m: Yamux, channel: YamuxChannel) {.async: (raises: []).} =
|
||||
try:
|
||||
await channel.join()
|
||||
except CancelledError:
|
||||
discard
|
||||
m.channels.del(channel.id)
|
||||
when defined(libp2p_yamux_metrics):
|
||||
libp2p_yamux_channels.set(
|
||||
m.lenBySrc(channel.isSrc).int64, [$channel.isSrc, $channel.peerId])
|
||||
if channel.isReset and channel.recvWindow > 0:
|
||||
m.flushed[channel.id] = channel.recvWindow
|
||||
|
||||
proc createStream(
|
||||
m: Yamux, id: uint32, isSrc: bool,
|
||||
recvWindow: int, maxSendQueueSize: int): YamuxChannel =
|
||||
# During initialization, recvWindow can be larger than maxRecvWindow.
|
||||
# This is because the peer we're connected to will always assume
|
||||
# that the initial recvWindow is 256k.
|
||||
# To solve this contradiction, no updateWindow will be sent until
|
||||
# recvWindow is less than maxRecvWindow
|
||||
proc newClosedRemotelyFut(): Future[void] {.async: (raises: [], raw: true).} =
|
||||
newFuture[void]()
|
||||
var stream = YamuxChannel(
|
||||
id: id,
|
||||
maxRecvWindow: recvWindow,
|
||||
recvWindow: if recvWindow > YamuxDefaultWindowSize: recvWindow else: YamuxDefaultWindowSize,
|
||||
sendWindow: YamuxDefaultWindowSize,
|
||||
maxSendQueueSize: maxSendQueueSize,
|
||||
isSrc: isSrc,
|
||||
conn: m.connection,
|
||||
receivedData: newAsyncEvent(),
|
||||
closedRemotely: newClosedRemotelyFut()
|
||||
)
|
||||
stream.objName = "YamuxStream"
|
||||
if isSrc:
|
||||
stream.dir = Direction.Out
|
||||
stream.timeout = m.outTimeout
|
||||
else:
|
||||
stream.dir = Direction.In
|
||||
stream.timeout = m.inTimeout
|
||||
stream.timeoutHandler =
|
||||
proc(): Future[void] {.async: (raises: [], raw: true).} =
|
||||
trace "Idle timeout expired, resetting YamuxChannel"
|
||||
stream.reset(isLocal = true)
|
||||
stream.initStream()
|
||||
stream.peerId = m.connection.peerId
|
||||
stream.observedAddr = m.connection.observedAddr
|
||||
stream.transportDir = m.connection.transportDir
|
||||
when defined(libp2p_agents_metrics):
|
||||
stream.shortAgent = m.connection.shortAgent
|
||||
m.channels[id] = stream
|
||||
asyncSpawn m.cleanupChannel(stream)
|
||||
trace "created channel", id, pid=m.connection.peerId
|
||||
when defined(libp2p_yamux_metrics):
|
||||
libp2p_yamux_channels.set(m.lenBySrc(isSrc).int64, [$isSrc, $stream.peerId])
|
||||
return stream
|
||||
|
||||
method close*(m: Yamux) {.async: (raises: []).} =
|
||||
if m.isClosed == true:
|
||||
trace "Already closed"
|
||||
return
|
||||
m.isClosed = true
|
||||
|
||||
trace "Closing yamux"
|
||||
let channels = toSeq(m.channels.values())
|
||||
for channel in channels:
|
||||
await channel.reset(isLocal = true)
|
||||
try: await m.connection.write(YamuxHeader.goAway(NormalTermination))
|
||||
except CancelledError as exc: trace "cancelled sending goAway", msg = exc.msg
|
||||
except LPStreamError as exc: trace "failed to send goAway", msg = exc.msg
|
||||
await m.connection.close()
|
||||
trace "Closed yamux"
|
||||
|
||||
proc handleStream(m: Yamux, channel: YamuxChannel) {.async: (raises: []).} =
|
||||
## Call the muxer stream handler for this channel
|
||||
##
|
||||
await m.streamHandler(channel)
|
||||
trace "finished handling stream"
|
||||
doAssert(channel.isClosed, "connection not closed by handler!")
|
||||
|
||||
method handle*(m: Yamux) {.async: (raises: []).} =
|
||||
trace "Starting yamux handler", pid=m.connection.peerId
|
||||
try:
|
||||
while not m.connection.atEof:
|
||||
trace "waiting for header"
|
||||
let header = await m.connection.readHeader()
|
||||
trace "got message", h = $header
|
||||
|
||||
case header.msgType:
|
||||
of Ping:
|
||||
if MsgFlags.Syn in header.flags:
|
||||
await m.connection.write(YamuxHeader.ping(MsgFlags.Ack, header.length))
|
||||
of GoAway:
|
||||
var status: GoAwayStatus
|
||||
if status.checkedEnumAssign(header.length): trace "Received go away", status
|
||||
else: trace "Received unexpected error go away"
|
||||
break
|
||||
of Data, WindowUpdate:
|
||||
if MsgFlags.Syn in header.flags:
|
||||
if header.streamId in m.channels:
|
||||
debug "Trying to create an existing channel, skipping", id=header.streamId
|
||||
else:
|
||||
if header.streamId in m.flushed:
|
||||
m.flushed.del(header.streamId)
|
||||
|
||||
if header.streamId mod 2 == m.currentId mod 2:
|
||||
debug "Peer used our reserved stream id, skipping", id=header.streamId, currentId=m.currentId, peerId=m.connection.peerId
|
||||
raise newException(YamuxError, "Peer used our reserved stream id")
|
||||
let newStream = m.createStream(header.streamId, false, m.windowSize, m.maxSendQueueSize)
|
||||
if m.channels.len >= m.maxChannCount:
|
||||
await newStream.reset()
|
||||
continue
|
||||
await newStream.open()
|
||||
asyncSpawn m.handleStream(newStream)
|
||||
elif header.streamId notin m.channels:
|
||||
# Flush the data
|
||||
m.flushed.withValue(header.streamId, flushed):
|
||||
if header.msgType == Data:
|
||||
flushed[].dec(int(header.length))
|
||||
if flushed[] < 0:
|
||||
raise newException(YamuxError,
|
||||
"Peer exhausted the recvWindow after reset")
|
||||
if header.length > 0:
|
||||
var buffer = newSeqUninitialized[byte](header.length)
|
||||
await m.connection.readExactly(
|
||||
addr buffer[0], int(header.length))
|
||||
do:
|
||||
raise newException(YamuxError,
|
||||
"Unknown stream ID: " & $header.streamId)
|
||||
continue
|
||||
|
||||
let channel =
|
||||
try:
|
||||
m.channels[header.streamId]
|
||||
except KeyError:
|
||||
raise newException(YamuxError,
|
||||
"Stream was cleaned up before handling data: " & $header.streamId)
|
||||
|
||||
if header.msgType == WindowUpdate:
|
||||
channel.sendWindow += int(header.length)
|
||||
await channel.trySend()
|
||||
else:
|
||||
if header.length.int > channel.recvWindow.int:
|
||||
# check before allocating the buffer
|
||||
raise newException(YamuxError, "Peer exhausted the recvWindow")
|
||||
|
||||
if header.length > 0:
|
||||
var buffer = newSeqUninitialized[byte](header.length)
|
||||
await m.connection.readExactly(addr buffer[0], int(header.length))
|
||||
trace "Msg Rcv", msg=shortLog(buffer)
|
||||
await channel.gotDataFromRemote(buffer)
|
||||
|
||||
if MsgFlags.Fin in header.flags:
|
||||
trace "remote closed channel"
|
||||
await channel.remoteClosed()
|
||||
if MsgFlags.Rst in header.flags:
|
||||
trace "remote reset channel"
|
||||
await channel.reset()
|
||||
except CancelledError as exc:
|
||||
debug "Unexpected cancellation in yamux handler", msg = exc.msg
|
||||
except LPStreamEOFError as exc:
|
||||
trace "Stream EOF", msg = exc.msg
|
||||
except LPStreamError as exc:
|
||||
debug "Unexpected stream exception in yamux read loop", msg = exc.msg
|
||||
except YamuxError as exc:
|
||||
trace "Closing yamux connection", error=exc.msg
|
||||
try:
|
||||
await m.connection.write(YamuxHeader.goAway(ProtocolError))
|
||||
except CancelledError, LPStreamError:
|
||||
discard
|
||||
except MuxerError as exc:
|
||||
debug "Unexpected muxer exception in yamux read loop", msg = exc.msg
|
||||
try:
|
||||
await m.connection.write(YamuxHeader.goAway(ProtocolError))
|
||||
except CancelledError, LPStreamError:
|
||||
discard
|
||||
finally:
|
||||
await m.close()
|
||||
trace "Stopped yamux handler"
|
||||
|
||||
method getStreams*(m: Yamux): seq[Connection] =
|
||||
for c in m.channels.values: result.add(c)
|
||||
|
||||
method newStream*(
|
||||
m: Yamux,
|
||||
name: string = "",
|
||||
lazy: bool = false
|
||||
): Future[Connection] {.async: (raises: [
|
||||
CancelledError, LPStreamError, MuxerError]).} =
|
||||
if m.channels.len > m.maxChannCount - 1:
|
||||
raise newException(TooManyChannels, "max allowed channel count exceeded")
|
||||
let stream = m.createStream(m.currentId, true, m.windowSize, m.maxSendQueueSize)
|
||||
m.currentId += 2
|
||||
if not lazy:
|
||||
await stream.open()
|
||||
return stream
|
||||
|
||||
proc new*(
|
||||
T: type[Yamux], conn: Connection,
|
||||
maxChannCount: int = MaxChannelCount,
|
||||
windowSize: int = YamuxDefaultWindowSize,
|
||||
maxSendQueueSize: int = MaxSendQueueSize,
|
||||
inTimeout: Duration = 5.minutes,
|
||||
outTimeout: Duration = 5.minutes): T =
|
||||
T(
|
||||
connection: conn,
|
||||
currentId: if conn.dir == Out: 1 else: 2,
|
||||
maxChannCount: maxChannCount,
|
||||
windowSize: windowSize,
|
||||
maxSendQueueSize: maxSendQueueSize,
|
||||
inTimeout: inTimeout,
|
||||
outTimeout: outTimeout
|
||||
)
|
||||
@@ -1,18 +1,19 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2021 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[streams, strutils, sets, sequtils],
|
||||
chronos, chronicles,
|
||||
dnsclientpkg/[protocol, types]
|
||||
chronos, chronicles, stew/byteutils,
|
||||
dnsclientpkg/[protocol, types],
|
||||
../utility
|
||||
|
||||
import
|
||||
nameresolver
|
||||
@@ -73,17 +74,11 @@ proc getDnsResponse(
|
||||
if not receivedDataFuture.finished:
|
||||
raise newException(IOError, "DNS server timeout")
|
||||
|
||||
var
|
||||
rawResponse = sock.getMessage()
|
||||
dataStream = newStringStream()
|
||||
dataStream.writeData(addr rawResponse[0], rawResponse.len)
|
||||
dataStream.setPosition(0)
|
||||
let rawResponse = sock.getMessage()
|
||||
# parseResponse can has a raises: [Exception, ..] because of
|
||||
# https://github.com/nim-lang/Nim/commit/035134de429b5d99c5607c5fae912762bebb6008
|
||||
# it can't actually raise though
|
||||
return parseResponse(dataStream)
|
||||
except CatchableError as exc: raise exc
|
||||
except Exception as exc: raiseAssert exc.msg
|
||||
return exceptionToAssert: parseResponse(string.fromBytes(rawResponse))
|
||||
finally:
|
||||
await sock.closeWait()
|
||||
|
||||
@@ -115,7 +110,12 @@ method resolveIp*(
|
||||
try:
|
||||
let resp = await fut
|
||||
for answer in resp.answers:
|
||||
resolvedAddresses.incl(answer.toString())
|
||||
# toString can has a raises: [Exception, ..] because of
|
||||
# https://github.com/nim-lang/Nim/commit/035134de429b5d99c5607c5fae912762bebb6008
|
||||
# it can't actually raise though
|
||||
resolvedAddresses.incl(
|
||||
exceptionToAssert(answer.toString())
|
||||
)
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except ValueError as e:
|
||||
@@ -145,9 +145,13 @@ method resolveTxt*(
|
||||
for _ in 0 ..< self.nameServers.len:
|
||||
let server = self.nameServers[0]
|
||||
try:
|
||||
# toString can has a raises: [Exception, ..] because of
|
||||
# https://github.com/nim-lang/Nim/commit/035134de429b5d99c5607c5fae912762bebb6008
|
||||
# it can't actually raise though
|
||||
let response = await getDnsResponse(server, address, TXT)
|
||||
trace "Got TXT response", server = $server, answer=response.answers.mapIt(it.toString())
|
||||
return response.answers.mapIt(it.toString())
|
||||
return exceptionToAssert:
|
||||
trace "Got TXT response", server = $server, answer=response.answers.mapIt(it.toString())
|
||||
response.answers.mapIt(it.toString())
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2021 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[streams, strutils, tables],
|
||||
std/tables,
|
||||
chronos, chronicles
|
||||
|
||||
import nameresolver
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2021 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[sugar, sets, sequtils, strutils]
|
||||
import
|
||||
import
|
||||
chronos,
|
||||
chronicles,
|
||||
stew/[endians2, byteutils]
|
||||
stew/endians2
|
||||
import ".."/[multiaddress, multicodec]
|
||||
|
||||
logScope:
|
||||
topics = "libp2p nameresolver"
|
||||
|
||||
type
|
||||
type
|
||||
NameResolver* = ref object of RootObj
|
||||
|
||||
method resolveTxt*(
|
||||
self: NameResolver,
|
||||
address: string): Future[seq[string]] {.async, base.} =
|
||||
## Get TXT record
|
||||
##
|
||||
##
|
||||
|
||||
doAssert(false, "Not implemented!")
|
||||
|
||||
@@ -36,21 +36,23 @@ method resolveIp*(
|
||||
port: Port,
|
||||
domain: Domain = Domain.AF_UNSPEC): Future[seq[TransportAddress]] {.async, base.} =
|
||||
## Resolve the specified address
|
||||
##
|
||||
##
|
||||
|
||||
doAssert(false, "Not implemented!")
|
||||
|
||||
proc getHostname*(ma: MultiAddress): string =
|
||||
let firstPart = ($ma[0].get()).split('/')
|
||||
if firstPart.len > 1: firstPart[2]
|
||||
let
|
||||
firstPart = ma[0].valueOr: return ""
|
||||
fpSplitted = ($firstPart).split('/', 2)
|
||||
if fpSplitted.len > 2: fpSplitted[2]
|
||||
else: ""
|
||||
|
||||
proc resolveDnsAddress(
|
||||
proc resolveOneAddress(
|
||||
self: NameResolver,
|
||||
ma: MultiAddress,
|
||||
domain: Domain = Domain.AF_UNSPEC,
|
||||
prefix = ""): Future[seq[MultiAddress]]
|
||||
{.async, raises: [Defect, MaError, TransportAddressError].} =
|
||||
{.async.} =
|
||||
#Resolve a single address
|
||||
var pbuf: array[2, byte]
|
||||
|
||||
@@ -61,29 +63,22 @@ proc resolveDnsAddress(
|
||||
let
|
||||
port = Port(fromBytesBE(uint16, pbuf))
|
||||
resolvedAddresses = await self.resolveIp(prefix & dnsval, port, domain)
|
||||
|
||||
|
||||
return collect(newSeqOfCap(4)):
|
||||
for address in resolvedAddresses:
|
||||
var createdAddress = MultiAddress.init(address).tryGet()[0].tryGet()
|
||||
for part in ma:
|
||||
if DNS.match(part.get()): continue
|
||||
if DNS.match(part.tryGet()): continue
|
||||
createdAddress &= part.tryGet()
|
||||
createdAddress
|
||||
|
||||
func matchDnsSuffix(m1, m2: MultiAddress): MaResult[bool] =
|
||||
for partMaybe in m1:
|
||||
let part = ?partMaybe
|
||||
if DNS.match(part): continue
|
||||
let entryProt = ?m2[?part.protoCode()]
|
||||
if entryProt != part:
|
||||
return ok(false)
|
||||
return ok(true)
|
||||
|
||||
proc resolveDnsAddr(
|
||||
proc resolveDnsAddr*(
|
||||
self: NameResolver,
|
||||
ma: MultiAddress,
|
||||
depth: int = 0): Future[seq[MultiAddress]]
|
||||
{.async.} =
|
||||
depth: int = 0): Future[seq[MultiAddress]] {.async.} =
|
||||
|
||||
if not DNSADDR.matchPartial(ma):
|
||||
return @[ma]
|
||||
|
||||
trace "Resolving dnsaddr", ma
|
||||
if depth > 6:
|
||||
@@ -101,21 +96,17 @@ proc resolveDnsAddr(
|
||||
if not entry.startsWith("dnsaddr="): continue
|
||||
let entryValue = MultiAddress.init(entry[8..^1]).tryGet()
|
||||
|
||||
if not matchDnsSuffix(ma, entryValue).tryGet(): continue
|
||||
if entryValue.contains(multiCodec("p2p")).tryGet() and ma.contains(multiCodec("p2p")).tryGet():
|
||||
if entryValue[multiCodec("p2p")] != ma[multiCodec("p2p")]:
|
||||
continue
|
||||
|
||||
# The spec is not clear wheter only DNSADDR can be recursived
|
||||
# or any DNS addr. Only handling DNSADDR because it's simpler
|
||||
# to avoid infinite recursion
|
||||
if DNSADDR.matchPartial(entryValue):
|
||||
let resolved = await self.resolveDnsAddr(entryValue, depth + 1)
|
||||
for r in resolved:
|
||||
result.add(r)
|
||||
else:
|
||||
result.add(entryValue)
|
||||
let resolved = await self.resolveDnsAddr(entryValue, depth + 1)
|
||||
for r in resolved:
|
||||
result.add(r)
|
||||
|
||||
if result.len == 0:
|
||||
debug "Failed to resolve any DNSADDR", ma
|
||||
return @[ma]
|
||||
debug "Failed to resolve a DNSADDR", ma
|
||||
return @[]
|
||||
return result
|
||||
|
||||
|
||||
@@ -127,17 +118,18 @@ proc resolveMAddress*(
|
||||
if not DNS.matchPartial(address):
|
||||
res.incl(address)
|
||||
else:
|
||||
let code = address[0].get().protoCode().get()
|
||||
let code = address[0].tryGet().protoCode().tryGet()
|
||||
let seq = case code:
|
||||
of multiCodec("dns"):
|
||||
await self.resolveDnsAddress(address)
|
||||
await self.resolveOneAddress(address)
|
||||
of multiCodec("dns4"):
|
||||
await self.resolveDnsAddress(address, Domain.AF_INET)
|
||||
await self.resolveOneAddress(address, Domain.AF_INET)
|
||||
of multiCodec("dns6"):
|
||||
await self.resolveDnsAddress(address, Domain.AF_INET6)
|
||||
await self.resolveOneAddress(address, Domain.AF_INET6)
|
||||
of multiCodec("dnsaddr"):
|
||||
await self.resolveDnsAddr(address)
|
||||
else:
|
||||
assert false
|
||||
@[address]
|
||||
for ad in seq:
|
||||
res.incl(ad)
|
||||
|
||||
86
libp2p/observedaddrmanager.nim
Normal file
86
libp2p/observedaddrmanager.nim
Normal file
@@ -0,0 +1,86 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[sequtils, tables, sugar]
|
||||
import chronos
|
||||
import multiaddress, multicodec
|
||||
|
||||
type
|
||||
## Manages observed MultiAddresses by reomte peers. It keeps track of the most observed IP and IP/Port.
|
||||
ObservedAddrManager* = ref object of RootObj
|
||||
observedIPsAndPorts: seq[MultiAddress]
|
||||
maxSize: int
|
||||
minCount: int
|
||||
|
||||
proc addObservation*(self:ObservedAddrManager, observedAddr: MultiAddress): bool =
|
||||
## Adds a new observed MultiAddress. If the number of observations exceeds maxSize, the oldest one is removed.
|
||||
if self.observedIPsAndPorts.len >= self.maxSize:
|
||||
self.observedIPsAndPorts.del(0)
|
||||
self.observedIPsAndPorts.add(observedAddr)
|
||||
return true
|
||||
|
||||
proc getProtocol(self: ObservedAddrManager, observations: seq[MultiAddress], multiCodec: MultiCodec): Opt[MultiAddress] =
|
||||
var countTable = toCountTable(observations)
|
||||
countTable.sort()
|
||||
var orderedPairs = toSeq(countTable.pairs)
|
||||
for (ma, count) in orderedPairs:
|
||||
let protoCode = (ma[0].flatMap(protoCode)).valueOr: continue
|
||||
if protoCode == multiCodec and count >= self.minCount:
|
||||
return Opt.some(ma)
|
||||
return Opt.none(MultiAddress)
|
||||
|
||||
proc getMostObservedProtocol(self: ObservedAddrManager, multiCodec: MultiCodec): Opt[MultiAddress] =
|
||||
## Returns the most observed IP address or none if the number of observations are less than minCount.
|
||||
let observedIPs = collect:
|
||||
for observedIp in self.observedIPsAndPorts:
|
||||
observedIp[0].valueOr: continue
|
||||
return self.getProtocol(observedIPs, multiCodec)
|
||||
|
||||
proc getMostObservedProtoAndPort(self: ObservedAddrManager, multiCodec: MultiCodec): Opt[MultiAddress] =
|
||||
## Returns the most observed IP/Port address or none if the number of observations are less than minCount.
|
||||
return self.getProtocol(self.observedIPsAndPorts, multiCodec)
|
||||
|
||||
proc getMostObservedProtosAndPorts*(self: ObservedAddrManager): seq[MultiAddress] =
|
||||
## Returns the most observed IP4/Port and IP6/Port address or an empty seq if the number of observations
|
||||
## are less than minCount.
|
||||
var res: seq[MultiAddress]
|
||||
self.getMostObservedProtoAndPort(multiCodec("ip4")).withValue(ip4):
|
||||
res.add(ip4)
|
||||
self.getMostObservedProtoAndPort(multiCodec("ip6")).withValue(ip6):
|
||||
res.add(ip6)
|
||||
return res
|
||||
|
||||
proc guessDialableAddr*(
|
||||
self: ObservedAddrManager,
|
||||
ma: MultiAddress): MultiAddress =
|
||||
## Replaces the first proto value of each listen address by the corresponding (matching the proto code) most observed value.
|
||||
## If the most observed value is not available, the original MultiAddress is returned.
|
||||
let
|
||||
maFirst = ma[0].valueOr: return ma
|
||||
maRest = ma[1..^1].valueOr: return ma
|
||||
maFirstProto = maFirst.protoCode().valueOr: return ma
|
||||
|
||||
let observedIP = self.getMostObservedProtocol(maFirstProto).valueOr: return ma
|
||||
return concat(observedIP, maRest).valueOr: ma
|
||||
|
||||
proc `$`*(self: ObservedAddrManager): string =
|
||||
## Returns a string representation of the ObservedAddrManager.
|
||||
return "IPs and Ports: " & $self.observedIPsAndPorts
|
||||
|
||||
proc new*(
|
||||
T: typedesc[ObservedAddrManager],
|
||||
maxSize = 10,
|
||||
minCount = 3): T =
|
||||
## Creates a new ObservedAddrManager.
|
||||
return T(
|
||||
observedIPsAndPorts: newSeq[MultiAddress](),
|
||||
maxSize: maxSize,
|
||||
minCount: minCount)
|
||||
@@ -1,25 +1,27 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implementes API for libp2p peer.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
{.push public.}
|
||||
|
||||
import
|
||||
std/[hashes, strutils],
|
||||
stew/[base58, results],
|
||||
chronicles,
|
||||
nimcrypto/utils,
|
||||
utility,
|
||||
./crypto/crypto, ./multicodec, ./multihash, ./vbuffer,
|
||||
./protobuf/minprotobuf
|
||||
|
||||
export results
|
||||
export results, utility
|
||||
|
||||
const
|
||||
maxInlineKeyLength* = 42
|
||||
@@ -38,12 +40,8 @@ func shortLog*(pid: PeerId): string =
|
||||
var spid = $pid
|
||||
if len(spid) > 10:
|
||||
spid[3] = '*'
|
||||
|
||||
# reminder to remove this once we stop supporting 1.2
|
||||
when (NimMajor, NimMinor) > (1, 2):
|
||||
spid.delete(4 .. spid.high - 6)
|
||||
else:
|
||||
spid.delete(4, spid.high - 6)
|
||||
|
||||
spid.delete(4 .. spid.high - 6)
|
||||
|
||||
spid
|
||||
|
||||
@@ -144,7 +142,7 @@ func init*(pid: var PeerId, data: string): bool =
|
||||
if Base58.decode(data, p, length) == Base58Status.Success:
|
||||
p.setLen(length)
|
||||
var opid: PeerId
|
||||
shallowCopy(opid.data, p)
|
||||
opid.data = p
|
||||
if opid.validate():
|
||||
pid = opid
|
||||
result = true
|
||||
@@ -180,21 +178,18 @@ func init*(t: typedesc[PeerId], seckey: PrivateKey): Result[PeerId, cstring] =
|
||||
## Create new peer id from private key ``seckey``.
|
||||
PeerId.init(? seckey.getPublicKey().orError(cstring("invalid private key")))
|
||||
|
||||
proc random*(t: typedesc[PeerId], rng = newRng()): Result[PeerId, cstring] =
|
||||
## Create new peer id with random public key.
|
||||
let randomKey = PrivateKey.random(Secp256k1, rng[])[]
|
||||
PeerId.init(randomKey).orError(cstring("failed to generate random key"))
|
||||
|
||||
func match*(pid: PeerId, pubkey: PublicKey): bool =
|
||||
## Returns ``true`` if ``pid`` matches public key ``pubkey``.
|
||||
let p = PeerId.init(pubkey)
|
||||
if p.isErr:
|
||||
false
|
||||
else:
|
||||
pid == p.get()
|
||||
PeerId.init(pubkey) == Result[PeerId, cstring].ok(pid)
|
||||
|
||||
func match*(pid: PeerId, seckey: PrivateKey): bool =
|
||||
## Returns ``true`` if ``pid`` matches private key ``seckey``.
|
||||
let p = PeerId.init(seckey)
|
||||
if p.isErr:
|
||||
false
|
||||
else:
|
||||
pid == p.get()
|
||||
PeerId.init(seckey) == Result[PeerId, cstring].ok(pid)
|
||||
|
||||
## Serialization/Deserialization helpers
|
||||
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2019 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
{.push public.}
|
||||
|
||||
import std/[options, sequtils]
|
||||
import std/sequtils
|
||||
import pkg/[chronos, chronicles, stew/results]
|
||||
import peerid, multiaddress, crypto/crypto, routing_record, errors
|
||||
import peerid, multiaddress, multicodec, crypto/crypto, routing_record, errors, utility
|
||||
|
||||
export peerid, multiaddress, crypto, routing_record, errors, results
|
||||
|
||||
## Our local peer info
|
||||
|
||||
type
|
||||
PeerInfoError* = LPError
|
||||
PeerInfoError* = object of LPError
|
||||
|
||||
PeerInfo* = ref object
|
||||
AddressMapper* =
|
||||
proc(listenAddrs: seq[MultiAddress]): Future[seq[MultiAddress]]
|
||||
{.gcsafe, raises: [].}
|
||||
|
||||
PeerInfo* {.public.} = ref object
|
||||
peerId*: PeerId
|
||||
addrs*: seq[MultiAddress]
|
||||
listenAddrs*: seq[MultiAddress]
|
||||
addrs: seq[MultiAddress]
|
||||
addressMappers*: seq[AddressMapper]
|
||||
protocols*: seq[string]
|
||||
protoVersion*: string
|
||||
agentVersion*: string
|
||||
@@ -33,6 +40,7 @@ type
|
||||
func shortLog*(p: PeerInfo): auto =
|
||||
(
|
||||
peerId: $p.peerId,
|
||||
listenAddrs: mapIt(p.listenAddrs, $it),
|
||||
addrs: mapIt(p.addrs, $it),
|
||||
protocols: mapIt(p.protocols, $it),
|
||||
protoVersion: p.protoVersion,
|
||||
@@ -40,32 +48,59 @@ func shortLog*(p: PeerInfo): auto =
|
||||
)
|
||||
chronicles.formatIt(PeerInfo): shortLog(it)
|
||||
|
||||
proc update*(p: PeerInfo) =
|
||||
let sprRes = SignedPeerRecord.init(
|
||||
proc update*(p: PeerInfo) {.async.} =
|
||||
p.addrs = p.listenAddrs
|
||||
for mapper in p.addressMappers:
|
||||
p.addrs = await mapper(p.addrs)
|
||||
|
||||
p.signedPeerRecord = SignedPeerRecord.init(
|
||||
p.privateKey,
|
||||
PeerRecord.init(p.peerId, p.addrs)
|
||||
).valueOr():
|
||||
info "Can't update the signed peer record"
|
||||
return
|
||||
|
||||
proc addrs*(p: PeerInfo): seq[MultiAddress] =
|
||||
p.addrs
|
||||
|
||||
proc fullAddrs*(p: PeerInfo): MaResult[seq[MultiAddress]] =
|
||||
let peerIdPart = ? MultiAddress.init(multiCodec("p2p"), p.peerId.data)
|
||||
var res: seq[MultiAddress]
|
||||
for address in p.addrs:
|
||||
res.add(? concat(address, peerIdPart))
|
||||
ok(res)
|
||||
|
||||
proc parseFullAddress*(ma: MultiAddress): MaResult[(PeerId, MultiAddress)] =
|
||||
let p2pPart = ? ma[^1]
|
||||
if ? p2pPart.protoCode != multiCodec("p2p"):
|
||||
return err("Missing p2p part from multiaddress!")
|
||||
|
||||
let res = (
|
||||
? PeerId.init(? p2pPart.protoArgument()).orErr("invalid peerid"),
|
||||
? ma[0 .. ^2]
|
||||
)
|
||||
if sprRes.isOk:
|
||||
p.signedPeerRecord = sprRes.get()
|
||||
else:
|
||||
discard
|
||||
#info "Can't update the signed peer record"
|
||||
ok(res)
|
||||
|
||||
proc parseFullAddress*(ma: string | seq[byte]): MaResult[(PeerId, MultiAddress)] =
|
||||
parseFullAddress(? MultiAddress.init(ma))
|
||||
|
||||
proc new*(
|
||||
p: typedesc[PeerInfo],
|
||||
key: PrivateKey,
|
||||
addrs: openArray[MultiAddress] = [],
|
||||
listenAddrs: openArray[MultiAddress] = [],
|
||||
protocols: openArray[string] = [],
|
||||
protoVersion: string = "",
|
||||
agentVersion: string = ""): PeerInfo
|
||||
{.raises: [Defect, PeerInfoError].} =
|
||||
agentVersion: string = "",
|
||||
addressMappers = newSeq[AddressMapper](),
|
||||
): PeerInfo
|
||||
{.raises: [LPError].} =
|
||||
|
||||
let pubkey = try:
|
||||
key.getPublicKey().tryGet()
|
||||
except CatchableError:
|
||||
raise newException(PeerInfoError, "invalid private key")
|
||||
|
||||
let peerId = PeerID.init(key).tryGet()
|
||||
|
||||
let peerId = PeerId.init(key).tryGet()
|
||||
|
||||
let peerInfo = PeerInfo(
|
||||
peerId: peerId,
|
||||
@@ -73,10 +108,9 @@ proc new*(
|
||||
privateKey: key,
|
||||
protoVersion: protoVersion,
|
||||
agentVersion: agentVersion,
|
||||
addrs: @addrs,
|
||||
listenAddrs: @listenAddrs,
|
||||
protocols: @protocols,
|
||||
addressMappers: addressMappers
|
||||
)
|
||||
|
||||
peerInfo.update()
|
||||
|
||||
return peerInfo
|
||||
|
||||
@@ -1,29 +1,49 @@
|
||||
## Nim-LibP2P
|
||||
## Copyright (c) 2021 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
## Stores generic informations about peers.
|
||||
runnableExamples:
|
||||
# Will keep info of all connected peers +
|
||||
# last 50 disconnected peers
|
||||
let peerStore = PeerStore.new(capacity = 50)
|
||||
|
||||
# Create a custom book type
|
||||
type MoodBook = ref object of PeerBook[string]
|
||||
|
||||
var somePeerId = PeerId.random().expect("get random key")
|
||||
|
||||
peerStore[MoodBook][somePeerId] = "Happy"
|
||||
doAssert peerStore[MoodBook][somePeerId] == "Happy"
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[tables, sets, options, macros],
|
||||
chronos,
|
||||
./crypto/crypto,
|
||||
./protocols/identify,
|
||||
./protocols/protocol,
|
||||
./peerid, ./peerinfo,
|
||||
./routing_record,
|
||||
./multiaddress
|
||||
./multiaddress,
|
||||
./stream/connection,
|
||||
./multistream,
|
||||
./muxers/muxer,
|
||||
utility
|
||||
|
||||
type
|
||||
#################
|
||||
# Handler types #
|
||||
#################
|
||||
|
||||
PeerBookChangeHandler* = proc(peerId: PeerId) {.gcsafe, raises: [Defect].}
|
||||
|
||||
PeerBookChangeHandler* = proc(peerId: PeerId) {.gcsafe, raises: [].}
|
||||
|
||||
#########
|
||||
# Books #
|
||||
#########
|
||||
@@ -33,46 +53,49 @@ type
|
||||
changeHandlers: seq[PeerBookChangeHandler]
|
||||
deletor: PeerBookChangeHandler
|
||||
|
||||
PeerBook*[T] = ref object of BasePeerBook
|
||||
PeerBook*[T] {.public.} = ref object of BasePeerBook
|
||||
book*: Table[PeerId, T]
|
||||
|
||||
SeqPeerBook*[T] = ref object of PeerBook[seq[T]]
|
||||
|
||||
AddressBook* = ref object of SeqPeerBook[MultiAddress]
|
||||
ProtoBook* = ref object of SeqPeerBook[string]
|
||||
KeyBook* = ref object of PeerBook[PublicKey]
|
||||
|
||||
AgentBook* = ref object of PeerBook[string]
|
||||
ProtoVersionBook* = ref object of PeerBook[string]
|
||||
SPRBook* = ref object of PeerBook[Envelope]
|
||||
|
||||
AddressBook* {.public.} = ref object of SeqPeerBook[MultiAddress]
|
||||
ProtoBook* {.public.} = ref object of SeqPeerBook[string]
|
||||
KeyBook* {.public.} = ref object of PeerBook[PublicKey]
|
||||
|
||||
AgentBook* {.public.} = ref object of PeerBook[string]
|
||||
ProtoVersionBook* {.public.} = ref object of PeerBook[string]
|
||||
SPRBook* {.public.} = ref object of PeerBook[Envelope]
|
||||
|
||||
####################
|
||||
# Peer store types #
|
||||
####################
|
||||
|
||||
PeerStore* = ref object
|
||||
PeerStore* {.public.} = ref object
|
||||
books: Table[string, BasePeerBook]
|
||||
identify: Identify
|
||||
capacity*: int
|
||||
toClean*: seq[PeerId]
|
||||
|
||||
proc new*(T: type PeerStore, capacity = 1000): PeerStore =
|
||||
T(capacity: capacity)
|
||||
|
||||
proc new*(T: type PeerStore, identify: Identify, capacity = 1000): PeerStore {.public.} =
|
||||
T(
|
||||
identify: identify,
|
||||
capacity: capacity
|
||||
)
|
||||
|
||||
#########################
|
||||
# Generic Peer Book API #
|
||||
#########################
|
||||
|
||||
proc `[]`*[T](peerBook: PeerBook[T],
|
||||
peerId: PeerId): T =
|
||||
## Get all the known metadata of a provided peer.
|
||||
peerId: PeerId): T {.public.} =
|
||||
## Get all known metadata of a provided peer, or default(T) if missing
|
||||
peerBook.book.getOrDefault(peerId)
|
||||
|
||||
proc `[]=`*[T](peerBook: PeerBook[T],
|
||||
peerId: PeerId,
|
||||
entry: T) =
|
||||
## Set metadata for a given peerId. This will replace any
|
||||
## previously stored metadata.
|
||||
|
||||
entry: T) {.public.} =
|
||||
## Set metadata for a given peerId.
|
||||
|
||||
peerBook.book[peerId] = entry
|
||||
|
||||
# Notify clients
|
||||
@@ -80,9 +103,9 @@ proc `[]=`*[T](peerBook: PeerBook[T],
|
||||
handler(peerId)
|
||||
|
||||
proc del*[T](peerBook: PeerBook[T],
|
||||
peerId: PeerId): bool =
|
||||
## Delete the provided peer from the book.
|
||||
|
||||
peerId: PeerId): bool {.public.} =
|
||||
## Delete the provided peer from the book. Returns whether the peer was in the book
|
||||
|
||||
if peerId notin peerBook.book:
|
||||
return false
|
||||
else:
|
||||
@@ -92,15 +115,16 @@ proc del*[T](peerBook: PeerBook[T],
|
||||
handler(peerId)
|
||||
return true
|
||||
|
||||
proc contains*[T](peerBook: PeerBook[T], peerId: PeerId): bool =
|
||||
proc contains*[T](peerBook: PeerBook[T], peerId: PeerId): bool {.public.} =
|
||||
peerId in peerBook.book
|
||||
|
||||
proc addHandler*[T](peerBook: PeerBook[T], handler: PeerBookChangeHandler) =
|
||||
proc addHandler*[T](peerBook: PeerBook[T], handler: PeerBookChangeHandler) {.public.} =
|
||||
## Adds a callback that will be called everytime the book changes
|
||||
peerBook.changeHandlers.add(handler)
|
||||
|
||||
proc len*[T](peerBook: PeerBook[T]): int = peerBook.book.len
|
||||
proc len*[T](peerBook: PeerBook[T]): int {.public.} = peerBook.book.len
|
||||
|
||||
##################
|
||||
##################
|
||||
# Peer Store API #
|
||||
##################
|
||||
macro getTypeName(t: type): untyped =
|
||||
@@ -108,7 +132,8 @@ macro getTypeName(t: type): untyped =
|
||||
let typ = getTypeImpl(t)[1]
|
||||
newLit(repr(typ.owner()) & "." & repr(typ))
|
||||
|
||||
proc `[]`*[T](p: PeerStore, typ: type[T]): T =
|
||||
proc `[]`*[T](p: PeerStore, typ: type[T]): T {.public.} =
|
||||
## Get a book from the PeerStore (ex: peerStore[AddressBook])
|
||||
let name = getTypeName(T)
|
||||
result = T(p.books.getOrDefault(name))
|
||||
if result.isNil:
|
||||
@@ -121,7 +146,7 @@ proc `[]`*[T](p: PeerStore, typ: type[T]): T =
|
||||
return result
|
||||
|
||||
proc del*(peerStore: PeerStore,
|
||||
peerId: PeerId) =
|
||||
peerId: PeerId) {.public.} =
|
||||
## Delete the provided peer from every book.
|
||||
for _, book in peerStore.books:
|
||||
book.deletor(peerId)
|
||||
@@ -133,17 +158,20 @@ proc updatePeerInfo*(
|
||||
if info.addrs.len > 0:
|
||||
peerStore[AddressBook][info.peerId] = info.addrs
|
||||
|
||||
if info.agentVersion.isSome:
|
||||
peerStore[AgentBook][info.peerId] = info.agentVersion.get().string
|
||||
info.pubkey.withValue(pubkey):
|
||||
peerStore[KeyBook][info.peerId] = pubkey
|
||||
|
||||
if info.protoVersion.isSome:
|
||||
peerStore[ProtoVersionBook][info.peerId] = info.protoVersion.get().string
|
||||
info.agentVersion.withValue(agentVersion):
|
||||
peerStore[AgentBook][info.peerId] = agentVersion.string
|
||||
|
||||
info.protoVersion.withValue(protoVersion):
|
||||
peerStore[ProtoVersionBook][info.peerId] = protoVersion.string
|
||||
|
||||
if info.protos.len > 0:
|
||||
peerStore[ProtoBook][info.peerId] = info.protos
|
||||
|
||||
if info.signedPeerRecord.isSome:
|
||||
peerStore[SPRBook][info.peerId] = info.signedPeerRecord.get()
|
||||
info.signedPeerRecord.withValue(signedPeerRecord):
|
||||
peerStore[SPRBook][info.peerId] = signedPeerRecord
|
||||
|
||||
let cleanupPos = peerStore.toClean.find(info.peerId)
|
||||
if cleanupPos >= 0:
|
||||
@@ -164,3 +192,34 @@ proc cleanup*(
|
||||
while peerStore.toClean.len > peerStore.capacity:
|
||||
peerStore.del(peerStore.toClean[0])
|
||||
peerStore.toClean.delete(0)
|
||||
|
||||
proc identify*(
|
||||
peerStore: PeerStore,
|
||||
muxer: Muxer) {.async.} =
|
||||
|
||||
# new stream for identify
|
||||
var stream = await muxer.newStream()
|
||||
if stream == nil:
|
||||
return
|
||||
|
||||
try:
|
||||
if (await MultistreamSelect.select(stream, peerStore.identify.codec())):
|
||||
let info = await peerStore.identify.identify(stream, stream.peerId)
|
||||
|
||||
when defined(libp2p_agents_metrics):
|
||||
var
|
||||
knownAgent = "unknown"
|
||||
shortAgent = info.agentVersion.get("").split("/")[0].safeToLowerAscii().get("")
|
||||
if KnownLibP2PAgentsSeq.contains(shortAgent):
|
||||
knownAgent = shortAgent
|
||||
muxer.connection.setShortAgent(knownAgent)
|
||||
|
||||
peerStore.updatePeerInfo(info)
|
||||
finally:
|
||||
await stream.closeWithEOF()
|
||||
|
||||
proc getMostObservedProtosAndPorts*(self: PeerStore): seq[MultiAddress] =
|
||||
return self.identify.observedAddrManager.getMostObservedProtosAndPorts()
|
||||
|
||||
proc guessDialableAddr*(self: PeerStore, ma: MultiAddress): MultiAddress =
|
||||
return self.identify.observedAddrManager.guessDialableAddr(ma)
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
## Nim-Libp2p
|
||||
## Copyright (c) 2018 Status Research & Development GmbH
|
||||
## Licensed under either of
|
||||
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
## at your option.
|
||||
## This file may not be copied, modified, or distributed except according to
|
||||
## those terms.
|
||||
# Nim-Libp2p
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
## This module implements minimal Google's ProtoBuf primitives.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
{.push raises: [].}
|
||||
|
||||
import ../varint, stew/[endians2, results]
|
||||
export results
|
||||
import ../varint, ../utility, stew/[endians2, results]
|
||||
export results, utility
|
||||
|
||||
const
|
||||
MaxMessageSize* = 1'u shl 22
|
||||
{.push public.}
|
||||
|
||||
const MaxMessageSize = 1'u shl 22
|
||||
|
||||
type
|
||||
ProtoFieldKind* = enum
|
||||
@@ -32,6 +33,7 @@ type
|
||||
buffer*: seq[byte]
|
||||
offset*: int
|
||||
length*: int
|
||||
maxSize*: uint
|
||||
|
||||
ProtoHeader* = object
|
||||
wire*: ProtoFieldKind
|
||||
@@ -67,12 +69,12 @@ type
|
||||
hint | hint32 | hint64 | float32 | float64
|
||||
|
||||
const
|
||||
SupportedWireTypes* = {
|
||||
int(ProtoFieldKind.Varint),
|
||||
int(ProtoFieldKind.Fixed64),
|
||||
int(ProtoFieldKind.Length),
|
||||
int(ProtoFieldKind.Fixed32)
|
||||
}
|
||||
SupportedWireTypes* = @[
|
||||
uint64(ProtoFieldKind.Varint),
|
||||
uint64(ProtoFieldKind.Fixed64),
|
||||
uint64(ProtoFieldKind.Length),
|
||||
uint64(ProtoFieldKind.Fixed32)
|
||||
]
|
||||
|
||||
template checkFieldNumber*(i: int) =
|
||||
doAssert((i > 0 and i < (1 shl 29)) and not(i >= 19000 and i <= 19999),
|
||||
@@ -117,23 +119,28 @@ proc vsizeof*(field: ProtoField): int {.inline.} =
|
||||
0
|
||||
|
||||
proc initProtoBuffer*(data: seq[byte], offset = 0,
|
||||
options: set[ProtoFlags] = {}): ProtoBuffer =
|
||||
options: set[ProtoFlags] = {},
|
||||
maxSize = MaxMessageSize): ProtoBuffer =
|
||||
## Initialize ProtoBuffer with shallow copy of ``data``.
|
||||
shallowCopy(result.buffer, data)
|
||||
result.buffer = data
|
||||
result.offset = offset
|
||||
result.options = options
|
||||
result.maxSize = maxSize
|
||||
|
||||
proc initProtoBuffer*(data: openArray[byte], offset = 0,
|
||||
options: set[ProtoFlags] = {}): ProtoBuffer =
|
||||
options: set[ProtoFlags] = {},
|
||||
maxSize = MaxMessageSize): ProtoBuffer =
|
||||
## Initialize ProtoBuffer with copy of ``data``.
|
||||
result.buffer = @data
|
||||
result.offset = offset
|
||||
result.options = options
|
||||
result.maxSize = maxSize
|
||||
|
||||
proc initProtoBuffer*(options: set[ProtoFlags] = {}): ProtoBuffer =
|
||||
proc initProtoBuffer*(options: set[ProtoFlags] = {}, maxSize = MaxMessageSize): ProtoBuffer =
|
||||
## Initialize ProtoBuffer with new sequence of capacity ``cap``.
|
||||
result.buffer = newSeq[byte]()
|
||||
result.options = options
|
||||
result.maxSize = maxSize
|
||||
if WithVarintLength in options:
|
||||
# Our buffer will start from position 10, so we can store length of buffer
|
||||
# in [0, 9].
|
||||
@@ -330,7 +337,7 @@ proc skipValue(data: var ProtoBuffer, header: ProtoHeader): ProtoResult[void] =
|
||||
var bsize = 0'u64
|
||||
if PB.getUVarint(data.toOpenArray(), length, bsize).isOk():
|
||||
data.offset += length
|
||||
if bsize <= uint64(MaxMessageSize):
|
||||
if bsize <= uint64(data.maxSize):
|
||||
if data.isEnough(int(bsize)):
|
||||
data.offset += int(bsize)
|
||||
ok()
|
||||
@@ -394,7 +401,7 @@ proc getValue[T:byte|char](data: var ProtoBuffer, header: ProtoHeader,
|
||||
outLength = 0
|
||||
if PB.getUVarint(data.toOpenArray(), length, bsize).isOk():
|
||||
data.offset += length
|
||||
if bsize <= uint64(MaxMessageSize):
|
||||
if bsize <= uint64(data.maxSize):
|
||||
if data.isEnough(int(bsize)):
|
||||
outLength = int(bsize)
|
||||
if len(outBytes) >= int(bsize):
|
||||
@@ -422,7 +429,7 @@ proc getValue[T:seq[byte]|string](data: var ProtoBuffer, header: ProtoHeader,
|
||||
|
||||
if PB.getUVarint(data.toOpenArray(), length, bsize).isOk():
|
||||
data.offset += length
|
||||
if bsize <= uint64(MaxMessageSize):
|
||||
if bsize <= uint64(data.maxSize):
|
||||
if data.isEnough(int(bsize)):
|
||||
outBytes.setLen(bsize)
|
||||
if bsize > 0'u64:
|
||||
@@ -569,26 +576,18 @@ proc getField*[T: seq[byte]|string](data: ProtoBuffer, field: int,
|
||||
proc getField*(pb: ProtoBuffer, field: int,
|
||||
output: var ProtoBuffer): ProtoResult[bool] {.inline.} =
|
||||
var buffer: seq[byte]
|
||||
let res = pb.getField(field, buffer)
|
||||
if res.isOk():
|
||||
if res.get():
|
||||
output = initProtoBuffer(buffer)
|
||||
ok(true)
|
||||
else:
|
||||
ok(false)
|
||||
if ? pb.getField(field, buffer):
|
||||
output = initProtoBuffer(buffer)
|
||||
ok(true)
|
||||
else:
|
||||
err(res.error)
|
||||
ok(false)
|
||||
|
||||
proc getRequiredField*[T](pb: ProtoBuffer, field: int,
|
||||
output: var T): ProtoResult[void] {.inline.} =
|
||||
let res = pb.getField(field, output)
|
||||
if res.isOk():
|
||||
if res.get():
|
||||
ok()
|
||||
else:
|
||||
err(RequiredFieldMissing)
|
||||
if ? pb.getField(field, output):
|
||||
ok()
|
||||
else:
|
||||
err(res.error)
|
||||
err(RequiredFieldMissing)
|
||||
|
||||
proc getRepeatedField*[T: seq[byte]|string](data: ProtoBuffer, field: int,
|
||||
output: var seq[T]): ProtoResult[bool] =
|
||||
@@ -668,14 +667,10 @@ proc getRepeatedField*[T: ProtoScalar](data: ProtoBuffer, field: int,
|
||||
|
||||
proc getRequiredRepeatedField*[T](pb: ProtoBuffer, field: int,
|
||||
output: var seq[T]): ProtoResult[void] {.inline.} =
|
||||
let res = pb.getRepeatedField(field, output)
|
||||
if res.isOk():
|
||||
if res.get():
|
||||
ok()
|
||||
else:
|
||||
err(RequiredFieldMissing)
|
||||
if ? pb.getRepeatedField(field, output):
|
||||
ok()
|
||||
else:
|
||||
err(res.error)
|
||||
err(RequiredFieldMissing)
|
||||
|
||||
proc getPackedRepeatedField*[T: ProtoScalar](data: ProtoBuffer, field: int,
|
||||
output: var seq[T]): ProtoResult[bool] =
|
||||
|
||||
70
libp2p/protocols/connectivity/autonat/client.nim
Normal file
70
libp2p/protocols/connectivity/autonat/client.nim
Normal file
@@ -0,0 +1,70 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import stew/results
|
||||
import chronos, chronicles
|
||||
import ../../../switch,
|
||||
../../../multiaddress,
|
||||
../../../peerid
|
||||
import core
|
||||
|
||||
logScope:
|
||||
topics = "libp2p autonat"
|
||||
|
||||
type
|
||||
AutonatClient* = ref object of RootObj
|
||||
|
||||
proc sendDial(conn: Connection, pid: PeerId, addrs: seq[MultiAddress]) {.async.} =
|
||||
let pb = AutonatDial(peerInfo: Opt.some(AutonatPeerInfo(
|
||||
id: Opt.some(pid),
|
||||
addrs: addrs
|
||||
))).encode()
|
||||
await conn.writeLp(pb.buffer)
|
||||
|
||||
method dialMe*(self: AutonatClient, switch: Switch, pid: PeerId, addrs: seq[MultiAddress] = newSeq[MultiAddress]()):
|
||||
Future[MultiAddress] {.base, async.} =
|
||||
|
||||
proc getResponseOrRaise(autonatMsg: Opt[AutonatMsg]): AutonatDialResponse {.raises: [AutonatError].} =
|
||||
autonatMsg.withValue(msg):
|
||||
if msg.msgType == DialResponse:
|
||||
msg.response.withValue(res):
|
||||
if not (res.status == Ok and res.ma.isNone()):
|
||||
return res
|
||||
raise newException(AutonatError, "Unexpected response")
|
||||
|
||||
let conn =
|
||||
try:
|
||||
if addrs.len == 0:
|
||||
await switch.dial(pid, @[AutonatCodec])
|
||||
else:
|
||||
await switch.dial(pid, addrs, AutonatCodec)
|
||||
except CatchableError as err:
|
||||
raise newException(AutonatError, "Unexpected error when dialling: " & err.msg, err)
|
||||
|
||||
# To bypass maxConnectionsPerPeer
|
||||
let incomingConnection = switch.connManager.expectConnection(pid, In)
|
||||
if incomingConnection.failed() and incomingConnection.error of AlreadyExpectingConnectionError:
|
||||
raise newException(AutonatError, incomingConnection.error.msg)
|
||||
defer:
|
||||
await conn.close()
|
||||
incomingConnection.cancel() # Safer to always try to cancel cause we aren't sure if the peer dialled us or not
|
||||
if incomingConnection.completed():
|
||||
await (await incomingConnection).connection.close()
|
||||
trace "sending Dial", addrs = switch.peerInfo.addrs
|
||||
await conn.sendDial(switch.peerInfo.peerId, switch.peerInfo.addrs)
|
||||
let response = getResponseOrRaise(AutonatMsg.decode(await conn.readLp(1024)))
|
||||
return case response.status:
|
||||
of ResponseStatus.Ok:
|
||||
response.ma.tryGet()
|
||||
of ResponseStatus.DialError:
|
||||
raise newException(AutonatUnreachableError, "Peer could not dial us back: " & response.text.get(""))
|
||||
else:
|
||||
raise newException(AutonatError, "Bad status " & $response.status & " " & response.text.get(""))
|
||||
139
libp2p/protocols/connectivity/autonat/core.nim
Normal file
139
libp2p/protocols/connectivity/autonat/core.nim
Normal file
@@ -0,0 +1,139 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import stew/[results, objects]
|
||||
import chronos, chronicles
|
||||
import ../../../multiaddress,
|
||||
../../../peerid,
|
||||
../../../errors
|
||||
|
||||
logScope:
|
||||
topics = "libp2p autonat"
|
||||
|
||||
const
|
||||
AutonatCodec* = "/libp2p/autonat/1.0.0"
|
||||
AddressLimit* = 8
|
||||
|
||||
type
|
||||
AutonatError* = object of LPError
|
||||
AutonatUnreachableError* = object of LPError
|
||||
|
||||
MsgType* = enum
|
||||
Dial = 0
|
||||
DialResponse = 1
|
||||
|
||||
ResponseStatus* = enum
|
||||
Ok = 0
|
||||
DialError = 100
|
||||
DialRefused = 101
|
||||
BadRequest = 200
|
||||
InternalError = 300
|
||||
|
||||
AutonatPeerInfo* = object
|
||||
id*: Opt[PeerId]
|
||||
addrs*: seq[MultiAddress]
|
||||
|
||||
AutonatDial* = object
|
||||
peerInfo*: Opt[AutonatPeerInfo]
|
||||
|
||||
AutonatDialResponse* = object
|
||||
status*: ResponseStatus
|
||||
text*: Opt[string]
|
||||
ma*: Opt[MultiAddress]
|
||||
|
||||
AutonatMsg* = object
|
||||
msgType*: MsgType
|
||||
dial*: Opt[AutonatDial]
|
||||
response*: Opt[AutonatDialResponse]
|
||||
|
||||
NetworkReachability* {.pure.} = enum
|
||||
Unknown, NotReachable, Reachable
|
||||
|
||||
proc encode(p: AutonatPeerInfo): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
p.id.withValue(id):
|
||||
result.write(1, id)
|
||||
for ma in p.addrs:
|
||||
result.write(2, ma.data.buffer)
|
||||
result.finish()
|
||||
|
||||
proc encode*(d: AutonatDial): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
result.write(1, MsgType.Dial.uint)
|
||||
var dial = initProtoBuffer()
|
||||
d.peerInfo.withValue(pinfo):
|
||||
dial.write(1, encode(pinfo))
|
||||
dial.finish()
|
||||
result.write(2, dial.buffer)
|
||||
result.finish()
|
||||
|
||||
proc encode*(r: AutonatDialResponse): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
result.write(1, MsgType.DialResponse.uint)
|
||||
var bufferResponse = initProtoBuffer()
|
||||
bufferResponse.write(1, r.status.uint)
|
||||
r.text.withValue(text):
|
||||
bufferResponse.write(2, text)
|
||||
r.ma.withValue(ma):
|
||||
bufferResponse.write(3, ma)
|
||||
bufferResponse.finish()
|
||||
result.write(3, bufferResponse.buffer)
|
||||
result.finish()
|
||||
|
||||
proc encode*(msg: AutonatMsg): ProtoBuffer =
|
||||
msg.dial.withValue(dial):
|
||||
return encode(dial)
|
||||
msg.response.withValue(res):
|
||||
return encode(res)
|
||||
|
||||
proc decode*(_: typedesc[AutonatMsg], buf: seq[byte]): Opt[AutonatMsg] =
|
||||
var
|
||||
msgTypeOrd: uint32
|
||||
pbDial: ProtoBuffer
|
||||
pbResponse: ProtoBuffer
|
||||
msg: AutonatMsg
|
||||
|
||||
let pb = initProtoBuffer(buf)
|
||||
|
||||
if ? pb.getField(1, msgTypeOrd).toOpt() and not checkedEnumAssign(msg.msgType, msgTypeOrd):
|
||||
return Opt.none(AutonatMsg)
|
||||
if ? pb.getField(2, pbDial).toOpt():
|
||||
var
|
||||
pbPeerInfo: ProtoBuffer
|
||||
dial: AutonatDial
|
||||
let r4 = ? pbDial.getField(1, pbPeerInfo).toOpt()
|
||||
|
||||
var peerInfo: AutonatPeerInfo
|
||||
if r4:
|
||||
var pid: PeerId
|
||||
let
|
||||
r5 = ? pbPeerInfo.getField(1, pid).toOpt()
|
||||
r6 = ? pbPeerInfo.getRepeatedField(2, peerInfo.addrs).toOpt()
|
||||
if r5: peerInfo.id = Opt.some(pid)
|
||||
dial.peerInfo = Opt.some(peerInfo)
|
||||
msg.dial = Opt.some(dial)
|
||||
|
||||
if ? pb.getField(3, pbResponse).toOpt():
|
||||
var
|
||||
statusOrd: uint
|
||||
text: string
|
||||
ma: MultiAddress
|
||||
response: AutonatDialResponse
|
||||
|
||||
if ? pbResponse.getField(1, statusOrd).optValue():
|
||||
if not checkedEnumAssign(response.status, statusOrd):
|
||||
return Opt.none(AutonatMsg)
|
||||
if ? pbResponse.getField(2, text).optValue():
|
||||
response.text = Opt.some(text)
|
||||
if ? pbResponse.getField(3, ma).optValue():
|
||||
response.ma = Opt.some(ma)
|
||||
msg.response = Opt.some(response)
|
||||
return Opt.some(msg)
|
||||
160
libp2p/protocols/connectivity/autonat/server.nim
Normal file
160
libp2p/protocols/connectivity/autonat/server.nim
Normal file
@@ -0,0 +1,160 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[sets, sequtils]
|
||||
import stew/results
|
||||
import chronos, chronicles
|
||||
import ../../protocol,
|
||||
../../../switch,
|
||||
../../../multiaddress,
|
||||
../../../multicodec,
|
||||
../../../peerid,
|
||||
../../../utils/[semaphore, future],
|
||||
../../../errors
|
||||
import core
|
||||
|
||||
export core
|
||||
|
||||
logScope:
|
||||
topics = "libp2p autonat"
|
||||
|
||||
type
|
||||
Autonat* = ref object of LPProtocol
|
||||
sem: AsyncSemaphore
|
||||
switch*: Switch
|
||||
dialTimeout: Duration
|
||||
|
||||
proc sendDial(conn: Connection, pid: PeerId, addrs: seq[MultiAddress]) {.async.} =
|
||||
let pb = AutonatDial(peerInfo: Opt.some(AutonatPeerInfo(
|
||||
id: Opt.some(pid),
|
||||
addrs: addrs
|
||||
))).encode()
|
||||
await conn.writeLp(pb.buffer)
|
||||
|
||||
proc sendResponseError(conn: Connection, status: ResponseStatus, text: string = "") {.async.} =
|
||||
let pb = AutonatDialResponse(
|
||||
status: status,
|
||||
text: if text == "": Opt.none(string) else: Opt.some(text),
|
||||
ma: Opt.none(MultiAddress)
|
||||
).encode()
|
||||
await conn.writeLp(pb.buffer)
|
||||
|
||||
proc sendResponseOk(conn: Connection, ma: MultiAddress) {.async.} =
|
||||
let pb = AutonatDialResponse(
|
||||
status: ResponseStatus.Ok,
|
||||
text: Opt.some("Ok"),
|
||||
ma: Opt.some(ma)
|
||||
).encode()
|
||||
await conn.writeLp(pb.buffer)
|
||||
|
||||
proc tryDial(autonat: Autonat, conn: Connection, addrs: seq[MultiAddress]) {.async.} =
|
||||
await autonat.sem.acquire()
|
||||
var futs: seq[Future[Opt[MultiAddress]]]
|
||||
try:
|
||||
# This is to bypass the per peer max connections limit
|
||||
let outgoingConnection = autonat.switch.connManager.expectConnection(conn.peerId, Out)
|
||||
if outgoingConnection.failed() and outgoingConnection.error of AlreadyExpectingConnectionError:
|
||||
await conn.sendResponseError(DialRefused, outgoingConnection.error.msg)
|
||||
return
|
||||
# Safer to always try to cancel cause we aren't sure if the connection was established
|
||||
defer: outgoingConnection.cancel()
|
||||
# tryDial is to bypass the global max connections limit
|
||||
futs = addrs.mapIt(autonat.switch.dialer.tryDial(conn.peerId, @[it]))
|
||||
let fut = await anyCompleted(futs).wait(autonat.dialTimeout)
|
||||
let ma = await fut
|
||||
ma.withValue(maddr):
|
||||
await conn.sendResponseOk(maddr)
|
||||
else:
|
||||
await conn.sendResponseError(DialError, "Missing observed address")
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except AllFuturesFailedError as exc:
|
||||
debug "All dial attempts failed", addrs, exc = exc.msg
|
||||
await conn.sendResponseError(DialError, "All dial attempts failed")
|
||||
except AsyncTimeoutError as exc:
|
||||
debug "Dial timeout", addrs, exc = exc.msg
|
||||
await conn.sendResponseError(DialError, "Dial timeout")
|
||||
except CatchableError as exc:
|
||||
debug "Unexpected error", addrs, exc = exc.msg
|
||||
await conn.sendResponseError(DialError, "Unexpected error")
|
||||
finally:
|
||||
autonat.sem.release()
|
||||
for f in futs:
|
||||
if not f.finished():
|
||||
f.cancel()
|
||||
|
||||
proc handleDial(autonat: Autonat, conn: Connection, msg: AutonatMsg): Future[void] =
|
||||
let dial = msg.dial.valueOr:
|
||||
return conn.sendResponseError(BadRequest, "Missing Dial")
|
||||
let peerInfo = dial.peerInfo.valueOr:
|
||||
return conn.sendResponseError(BadRequest, "Missing Peer Info")
|
||||
peerInfo.id.withValue(id):
|
||||
if id != conn.peerId:
|
||||
return conn.sendResponseError(BadRequest, "PeerId mismatch")
|
||||
|
||||
let observedAddr = conn.observedAddr.valueOr:
|
||||
return conn.sendResponseError(BadRequest, "Missing observed address")
|
||||
|
||||
var isRelayed = observedAddr.contains(multiCodec("p2p-circuit")).valueOr:
|
||||
return conn.sendResponseError(DialRefused, "Invalid observed address")
|
||||
if isRelayed:
|
||||
return conn.sendResponseError(DialRefused, "Refused to dial a relayed observed address")
|
||||
let hostIp = observedAddr[0].valueOr:
|
||||
return conn.sendResponseError(InternalError, "Wrong observed address")
|
||||
if not IP.match(hostIp):
|
||||
return conn.sendResponseError(InternalError, "Expected an IP address")
|
||||
var addrs = initHashSet[MultiAddress]()
|
||||
addrs.incl(observedAddr)
|
||||
trace "addrs received", addrs = peerInfo.addrs
|
||||
for ma in peerInfo.addrs:
|
||||
isRelayed = ma.contains(multiCodec("p2p-circuit")).valueOr: continue
|
||||
let maFirst = ma[0].valueOr: continue
|
||||
if not DNS_OR_IP.match(maFirst): continue
|
||||
|
||||
try:
|
||||
addrs.incl(
|
||||
if maFirst == hostIp:
|
||||
ma
|
||||
else:
|
||||
let maEnd = ma[1..^1].valueOr: continue
|
||||
hostIp & maEnd
|
||||
)
|
||||
except LPError as exc:
|
||||
continue
|
||||
if len(addrs) >= AddressLimit:
|
||||
break
|
||||
|
||||
if len(addrs) == 0:
|
||||
return conn.sendResponseError(DialRefused, "No dialable address")
|
||||
let addrsSeq = toSeq(addrs)
|
||||
trace "trying to dial", addrs = addrsSeq
|
||||
return autonat.tryDial(conn, addrsSeq)
|
||||
|
||||
proc new*(T: typedesc[Autonat], switch: Switch, semSize: int = 1, dialTimeout = 15.seconds): T =
|
||||
let autonat = T(switch: switch, sem: newAsyncSemaphore(semSize), dialTimeout: dialTimeout)
|
||||
proc handleStream(conn: Connection, proto: string) {.async.} =
|
||||
try:
|
||||
let msg = AutonatMsg.decode(await conn.readLp(1024)).valueOr:
|
||||
raise newException(AutonatError, "Received malformed message")
|
||||
if msg.msgType != MsgType.Dial:
|
||||
raise newException(AutonatError, "Message type should be dial")
|
||||
await autonat.handleDial(conn, msg)
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
debug "exception in autonat handler", exc = exc.msg, conn
|
||||
finally:
|
||||
trace "exiting autonat handler", conn
|
||||
await conn.close()
|
||||
|
||||
autonat.handler = handleStream
|
||||
autonat.codec = AutonatCodec
|
||||
autonat
|
||||
217
libp2p/protocols/connectivity/autonat/service.nim
Normal file
217
libp2p/protocols/connectivity/autonat/service.nim
Normal file
@@ -0,0 +1,217 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[deques, sequtils]
|
||||
import chronos, metrics
|
||||
import ../../../switch
|
||||
import ../../../wire
|
||||
import client
|
||||
from core import NetworkReachability, AutonatUnreachableError
|
||||
import ../../../utils/heartbeat
|
||||
import ../../../crypto/crypto
|
||||
|
||||
export core.NetworkReachability
|
||||
|
||||
logScope:
|
||||
topics = "libp2p autonatservice"
|
||||
|
||||
declarePublicGauge(libp2p_autonat_reachability_confidence, "autonat reachability confidence", labels = ["reachability"])
|
||||
|
||||
type
|
||||
AutonatService* = ref object of Service
|
||||
newConnectedPeerHandler: PeerEventHandler
|
||||
addressMapper: AddressMapper
|
||||
scheduleHandle: Future[void]
|
||||
networkReachability*: NetworkReachability
|
||||
confidence: Opt[float]
|
||||
answers: Deque[NetworkReachability]
|
||||
autonatClient: AutonatClient
|
||||
statusAndConfidenceHandler: StatusAndConfidenceHandler
|
||||
rng: ref HmacDrbgContext
|
||||
scheduleInterval: Opt[Duration]
|
||||
askNewConnectedPeers: bool
|
||||
numPeersToAsk: int
|
||||
maxQueueSize: int
|
||||
minConfidence: float
|
||||
dialTimeout: Duration
|
||||
enableAddressMapper: bool
|
||||
|
||||
StatusAndConfidenceHandler* = proc (networkReachability: NetworkReachability, confidence: Opt[float]): Future[void] {.gcsafe, raises: [].}
|
||||
|
||||
proc new*(
|
||||
T: typedesc[AutonatService],
|
||||
autonatClient: AutonatClient,
|
||||
rng: ref HmacDrbgContext,
|
||||
scheduleInterval: Opt[Duration] = Opt.none(Duration),
|
||||
askNewConnectedPeers = true,
|
||||
numPeersToAsk: int = 5,
|
||||
maxQueueSize: int = 10,
|
||||
minConfidence: float = 0.3,
|
||||
dialTimeout = 30.seconds,
|
||||
enableAddressMapper = true): T =
|
||||
return T(
|
||||
scheduleInterval: scheduleInterval,
|
||||
networkReachability: Unknown,
|
||||
confidence: Opt.none(float),
|
||||
answers: initDeque[NetworkReachability](),
|
||||
autonatClient: autonatClient,
|
||||
rng: rng,
|
||||
askNewConnectedPeers: askNewConnectedPeers,
|
||||
numPeersToAsk: numPeersToAsk,
|
||||
maxQueueSize: maxQueueSize,
|
||||
minConfidence: minConfidence,
|
||||
dialTimeout: dialTimeout,
|
||||
enableAddressMapper: enableAddressMapper)
|
||||
|
||||
proc callHandler(self: AutonatService) {.async.} =
|
||||
if not isNil(self.statusAndConfidenceHandler):
|
||||
await self.statusAndConfidenceHandler(self.networkReachability, self.confidence)
|
||||
|
||||
proc hasEnoughIncomingSlots(switch: Switch): bool =
|
||||
# we leave some margin instead of comparing to 0 as a peer could connect to us while we are asking for the dial back
|
||||
return switch.connManager.slotsAvailable(In) >= 2
|
||||
|
||||
proc doesPeerHaveIncomingConn(switch: Switch, peerId: PeerId): bool =
|
||||
return switch.connManager.selectMuxer(peerId, In) != nil
|
||||
|
||||
proc handleAnswer(self: AutonatService, ans: NetworkReachability): Future[bool] {.async.} =
|
||||
|
||||
if ans == Unknown:
|
||||
return
|
||||
|
||||
let oldNetworkReachability = self.networkReachability
|
||||
let oldConfidence = self.confidence
|
||||
|
||||
if self.answers.len == self.maxQueueSize:
|
||||
self.answers.popFirst()
|
||||
self.answers.addLast(ans)
|
||||
|
||||
self.networkReachability = Unknown
|
||||
self.confidence = Opt.none(float)
|
||||
const reachabilityPriority = [Reachable, NotReachable]
|
||||
for reachability in reachabilityPriority:
|
||||
let confidence = self.answers.countIt(it == reachability) / self.maxQueueSize
|
||||
libp2p_autonat_reachability_confidence.set(value = confidence, labelValues = [$reachability])
|
||||
if self.confidence.isNone and confidence >= self.minConfidence:
|
||||
self.networkReachability = reachability
|
||||
self.confidence = Opt.some(confidence)
|
||||
|
||||
debug "Current status", currentStats = $self.networkReachability, confidence = $self.confidence, answers = self.answers
|
||||
|
||||
# Return whether anything has changed
|
||||
return self.networkReachability != oldNetworkReachability or self.confidence != oldConfidence
|
||||
|
||||
proc askPeer(self: AutonatService, switch: Switch, peerId: PeerId): Future[NetworkReachability] {.async.} =
|
||||
logScope:
|
||||
peerId = $peerId
|
||||
|
||||
if doesPeerHaveIncomingConn(switch, peerId):
|
||||
return Unknown
|
||||
|
||||
if not hasEnoughIncomingSlots(switch):
|
||||
debug "No incoming slots available, not asking peer", incomingSlotsAvailable=switch.connManager.slotsAvailable(In)
|
||||
return Unknown
|
||||
|
||||
trace "Asking peer for reachability"
|
||||
let ans =
|
||||
try:
|
||||
discard await self.autonatClient.dialMe(switch, peerId).wait(self.dialTimeout)
|
||||
debug "dialMe answer is reachable"
|
||||
Reachable
|
||||
except AutonatUnreachableError as error:
|
||||
debug "dialMe answer is not reachable", msg = error.msg
|
||||
NotReachable
|
||||
except AsyncTimeoutError as error:
|
||||
debug "dialMe timed out", msg = error.msg
|
||||
Unknown
|
||||
except CatchableError as error:
|
||||
debug "dialMe unexpected error", msg = error.msg
|
||||
Unknown
|
||||
let hasReachabilityOrConfidenceChanged = await self.handleAnswer(ans)
|
||||
if hasReachabilityOrConfidenceChanged:
|
||||
await self.callHandler()
|
||||
await switch.peerInfo.update()
|
||||
return ans
|
||||
|
||||
proc askConnectedPeers(self: AutonatService, switch: Switch) {.async.} =
|
||||
trace "Asking peers for reachability"
|
||||
var peers = switch.connectedPeers(Direction.Out)
|
||||
self.rng.shuffle(peers)
|
||||
var answersFromPeers = 0
|
||||
for peer in peers:
|
||||
if answersFromPeers >= self.numPeersToAsk:
|
||||
break
|
||||
if not hasEnoughIncomingSlots(switch):
|
||||
debug "No incoming slots available, not asking peers", incomingSlotsAvailable=switch.connManager.slotsAvailable(In)
|
||||
break
|
||||
if (await askPeer(self, switch, peer)) != Unknown:
|
||||
answersFromPeers.inc()
|
||||
|
||||
proc schedule(service: AutonatService, switch: Switch, interval: Duration) {.async.} =
|
||||
heartbeat "Scheduling AutonatService run", interval:
|
||||
await service.run(switch)
|
||||
|
||||
proc addressMapper(
|
||||
self: AutonatService,
|
||||
peerStore: PeerStore,
|
||||
listenAddrs: seq[MultiAddress]): Future[seq[MultiAddress]] {.async.} =
|
||||
|
||||
if self.networkReachability != NetworkReachability.Reachable:
|
||||
return listenAddrs
|
||||
|
||||
var addrs = newSeq[MultiAddress]()
|
||||
for listenAddr in listenAddrs:
|
||||
var processedMA = listenAddr
|
||||
try:
|
||||
if not listenAddr.isPublicMA() and self.networkReachability == NetworkReachability.Reachable:
|
||||
processedMA = peerStore.guessDialableAddr(listenAddr) # handle manual port forwarding
|
||||
except CatchableError as exc:
|
||||
debug "Error while handling address mapper", msg = exc.msg
|
||||
addrs.add(processedMA)
|
||||
return addrs
|
||||
|
||||
method setup*(self: AutonatService, switch: Switch): Future[bool] {.async.} =
|
||||
self.addressMapper = proc (listenAddrs: seq[MultiAddress]): Future[seq[MultiAddress]] {.async.} =
|
||||
return await addressMapper(self, switch.peerStore, listenAddrs)
|
||||
|
||||
info "Setting up AutonatService"
|
||||
let hasBeenSetup = await procCall Service(self).setup(switch)
|
||||
if hasBeenSetup:
|
||||
if self.askNewConnectedPeers:
|
||||
self.newConnectedPeerHandler = proc (peerId: PeerId, event: PeerEvent): Future[void] {.async.} =
|
||||
discard askPeer(self, switch, peerId)
|
||||
switch.connManager.addPeerEventHandler(self.newConnectedPeerHandler, PeerEventKind.Joined)
|
||||
self.scheduleInterval.withValue(interval):
|
||||
self.scheduleHandle = schedule(self, switch, interval)
|
||||
if self.enableAddressMapper:
|
||||
switch.peerInfo.addressMappers.add(self.addressMapper)
|
||||
return hasBeenSetup
|
||||
|
||||
method run*(self: AutonatService, switch: Switch) {.async, public.} =
|
||||
trace "Running AutonatService"
|
||||
await askConnectedPeers(self, switch)
|
||||
|
||||
method stop*(self: AutonatService, switch: Switch): Future[bool] {.async, public.} =
|
||||
info "Stopping AutonatService"
|
||||
let hasBeenStopped = await procCall Service(self).stop(switch)
|
||||
if hasBeenStopped:
|
||||
if not isNil(self.scheduleHandle):
|
||||
self.scheduleHandle.cancel()
|
||||
self.scheduleHandle = nil
|
||||
if not isNil(self.newConnectedPeerHandler):
|
||||
switch.connManager.removePeerEventHandler(self.newConnectedPeerHandler, PeerEventKind.Joined)
|
||||
if self.enableAddressMapper:
|
||||
switch.peerInfo.addressMappers.keepItIf(it != self.addressMapper)
|
||||
await switch.peerInfo.update()
|
||||
return hasBeenStopped
|
||||
|
||||
proc statusAndConfidenceHandler*(self: AutonatService, statusAndConfidenceHandler: StatusAndConfidenceHandler) =
|
||||
self.statusAndConfidenceHandler = statusAndConfidenceHandler
|
||||
89
libp2p/protocols/connectivity/dcutr/client.nim
Normal file
89
libp2p/protocols/connectivity/dcutr/client.nim
Normal file
@@ -0,0 +1,89 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/sequtils
|
||||
|
||||
import stew/results
|
||||
import chronos, chronicles
|
||||
|
||||
import core
|
||||
import ../../protocol,
|
||||
../../../stream/connection,
|
||||
../../../switch,
|
||||
../../../utils/future
|
||||
|
||||
export DcutrError
|
||||
|
||||
type
|
||||
DcutrClient* = ref object
|
||||
connectTimeout: Duration
|
||||
maxDialableAddrs: int
|
||||
|
||||
logScope:
|
||||
topics = "libp2p dcutrclient"
|
||||
|
||||
proc new*(T: typedesc[DcutrClient], connectTimeout = 15.seconds, maxDialableAddrs = 8): T =
|
||||
return T(connectTimeout: connectTimeout, maxDialableAddrs: maxDialableAddrs)
|
||||
|
||||
proc startSync*(self: DcutrClient, switch: Switch, remotePeerId: PeerId, addrs: seq[MultiAddress]) {.async.} =
|
||||
logScope:
|
||||
peerId = switch.peerInfo.peerId
|
||||
|
||||
var
|
||||
peerDialableAddrs: seq[MultiAddress]
|
||||
stream: Connection
|
||||
try:
|
||||
var ourDialableAddrs = getHolePunchableAddrs(addrs)
|
||||
if ourDialableAddrs.len == 0:
|
||||
debug "Dcutr initiator has no supported dialable addresses. Aborting Dcutr.", addrs
|
||||
return
|
||||
|
||||
stream = await switch.dial(remotePeerId, DcutrCodec)
|
||||
await stream.send(MsgType.Connect, addrs)
|
||||
debug "Dcutr initiator has sent a Connect message."
|
||||
let rttStart = Moment.now()
|
||||
let connectAnswer = DcutrMsg.decode(await stream.readLp(1024))
|
||||
|
||||
peerDialableAddrs = getHolePunchableAddrs(connectAnswer.addrs)
|
||||
if peerDialableAddrs.len == 0:
|
||||
debug "Dcutr receiver has no supported dialable addresses to connect to. Aborting Dcutr.", addrs=connectAnswer.addrs
|
||||
return
|
||||
|
||||
let rttEnd = Moment.now()
|
||||
debug "Dcutr initiator has received a Connect message back.", connectAnswer
|
||||
let halfRtt = (rttEnd - rttStart) div 2'i64
|
||||
await stream.send(MsgType.Sync, @[])
|
||||
debug "Dcutr initiator has sent a Sync message."
|
||||
await sleepAsync(halfRtt)
|
||||
|
||||
if peerDialableAddrs.len > self.maxDialableAddrs:
|
||||
peerDialableAddrs = peerDialableAddrs[0..<self.maxDialableAddrs]
|
||||
var futs = peerDialableAddrs.mapIt(switch.connect(stream.peerId, @[it], forceDial = true, reuseConnection = false, dir = Direction.In))
|
||||
try:
|
||||
discard await anyCompleted(futs).wait(self.connectTimeout)
|
||||
debug "Dcutr initiator has directly connected to the remote peer."
|
||||
finally:
|
||||
for fut in futs: fut.cancel()
|
||||
except CancelledError as err:
|
||||
raise err
|
||||
except AllFuturesFailedError as err:
|
||||
debug "Dcutr initiator could not connect to the remote peer, all connect attempts failed", peerDialableAddrs, msg = err.msg
|
||||
raise newException(DcutrError, "Dcutr initiator could not connect to the remote peer, all connect attempts failed", err)
|
||||
except AsyncTimeoutError as err:
|
||||
debug "Dcutr initiator could not connect to the remote peer, all connect attempts timed out", peerDialableAddrs, msg = err.msg
|
||||
raise newException(DcutrError, "Dcutr initiator could not connect to the remote peer, all connect attempts timed out", err)
|
||||
except CatchableError as err:
|
||||
debug "Unexpected error when Dcutr initiator tried to connect to the remote peer", err = err.msg
|
||||
raise newException(DcutrError, "Unexpected error when Dcutr initiator tried to connect to the remote peer", err)
|
||||
finally:
|
||||
if stream != nil:
|
||||
await stream.close()
|
||||
|
||||
65
libp2p/protocols/connectivity/dcutr/core.nim
Normal file
65
libp2p/protocols/connectivity/dcutr/core.nim
Normal file
@@ -0,0 +1,65 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/sequtils
|
||||
|
||||
import chronos
|
||||
import stew/objects
|
||||
|
||||
import ../../../multiaddress,
|
||||
../../../errors,
|
||||
../../../stream/connection
|
||||
|
||||
export multiaddress
|
||||
|
||||
const
|
||||
DcutrCodec* = "/libp2p/dcutr"
|
||||
|
||||
type
|
||||
MsgType* = enum
|
||||
Connect = 100
|
||||
Sync = 300
|
||||
|
||||
DcutrMsg* = object
|
||||
msgType*: MsgType
|
||||
addrs*: seq[MultiAddress]
|
||||
|
||||
DcutrError* = object of LPError
|
||||
|
||||
proc encode*(msg: DcutrMsg): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
result.write(1, msg.msgType.uint)
|
||||
for addr in msg.addrs:
|
||||
result.write(2, addr)
|
||||
result.finish()
|
||||
|
||||
proc decode*(_: typedesc[DcutrMsg], buf: seq[byte]): DcutrMsg {.raises: [DcutrError].} =
|
||||
var
|
||||
msgTypeOrd: uint32
|
||||
dcutrMsg: DcutrMsg
|
||||
var pb = initProtoBuffer(buf)
|
||||
var r1 = pb.getField(1, msgTypeOrd)
|
||||
let r2 = pb.getRepeatedField(2, dcutrMsg.addrs)
|
||||
if r1.isErr or r2.isErr or not checkedEnumAssign(dcutrMsg.msgType, msgTypeOrd):
|
||||
raise newException(DcutrError, "Received malformed message")
|
||||
return dcutrMsg
|
||||
|
||||
proc send*(conn: Connection, msgType: MsgType, addrs: seq[MultiAddress]) {.async.} =
|
||||
let pb = DcutrMsg(msgType: msgType, addrs: addrs).encode()
|
||||
await conn.writeLp(pb.buffer)
|
||||
|
||||
proc getHolePunchableAddrs*(addrs: seq[MultiAddress]): seq[MultiAddress] {.raises: [LPError]} =
|
||||
var result = newSeq[MultiAddress]()
|
||||
for a in addrs:
|
||||
# This is necessary to also accept addrs like /ip4/198.51.100/tcp/1234/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N
|
||||
if [TCP, mapAnd(TCP_DNS, P2PPattern), mapAnd(TCP_IP, P2PPattern)].anyIt(it.match(a)):
|
||||
result.add(a[0..1].tryGet())
|
||||
return result
|
||||
79
libp2p/protocols/connectivity/dcutr/server.nim
Normal file
79
libp2p/protocols/connectivity/dcutr/server.nim
Normal file
@@ -0,0 +1,79 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[sets, sequtils]
|
||||
import stew/[results, objects]
|
||||
import chronos, chronicles
|
||||
|
||||
import core
|
||||
import ../../protocol,
|
||||
../../../stream/connection,
|
||||
../../../switch,
|
||||
../../../utils/future
|
||||
|
||||
export chronicles
|
||||
|
||||
type Dcutr* = ref object of LPProtocol
|
||||
|
||||
logScope:
|
||||
topics = "libp2p dcutr"
|
||||
|
||||
proc new*(T: typedesc[Dcutr], switch: Switch, connectTimeout = 15.seconds, maxDialableAddrs = 8): T =
|
||||
|
||||
proc handleStream(stream: Connection, proto: string) {.async.} =
|
||||
var peerDialableAddrs: seq[MultiAddress]
|
||||
try:
|
||||
let connectMsg = DcutrMsg.decode(await stream.readLp(1024))
|
||||
debug "Dcutr receiver received a Connect message.", connectMsg
|
||||
|
||||
var ourAddrs = switch.peerStore.getMostObservedProtosAndPorts() # likely empty when the peer is reachable
|
||||
if ourAddrs.len == 0:
|
||||
# this list should be the same as the peer's public addrs when it is reachable
|
||||
ourAddrs = switch.peerInfo.listenAddrs.mapIt(switch.peerStore.guessDialableAddr(it))
|
||||
var ourDialableAddrs = getHolePunchableAddrs(ourAddrs)
|
||||
if ourDialableAddrs.len == 0:
|
||||
debug "Dcutr receiver has no supported dialable addresses. Aborting Dcutr.", ourAddrs
|
||||
return
|
||||
|
||||
await stream.send(MsgType.Connect, ourAddrs)
|
||||
debug "Dcutr receiver has sent a Connect message back."
|
||||
let syncMsg = DcutrMsg.decode(await stream.readLp(1024))
|
||||
debug "Dcutr receiver has received a Sync message.", syncMsg
|
||||
|
||||
peerDialableAddrs = getHolePunchableAddrs(connectMsg.addrs)
|
||||
if peerDialableAddrs.len == 0:
|
||||
debug "Dcutr initiator has no supported dialable addresses to connect to. Aborting Dcutr.", addrs=connectMsg.addrs
|
||||
return
|
||||
|
||||
if peerDialableAddrs.len > maxDialableAddrs:
|
||||
peerDialableAddrs = peerDialableAddrs[0..<maxDialableAddrs]
|
||||
var futs = peerDialableAddrs.mapIt(switch.connect(stream.peerId, @[it], forceDial = true, reuseConnection = false, dir = Direction.Out))
|
||||
try:
|
||||
discard await anyCompleted(futs).wait(connectTimeout)
|
||||
debug "Dcutr receiver has directly connected to the remote peer."
|
||||
finally:
|
||||
for fut in futs: fut.cancel()
|
||||
except CancelledError as err:
|
||||
raise err
|
||||
except AllFuturesFailedError as err:
|
||||
debug "Dcutr receiver could not connect to the remote peer, " &
|
||||
"all connect attempts failed", peerDialableAddrs, msg = err.msg
|
||||
except AsyncTimeoutError as err:
|
||||
debug "Dcutr receiver could not connect to the remote peer, " &
|
||||
"all connect attempts timed out", peerDialableAddrs, msg = err.msg
|
||||
except CatchableError as err:
|
||||
warn "Unexpected error when Dcutr receiver tried to connect " &
|
||||
"to the remote peer", msg = err.msg
|
||||
|
||||
let self = T()
|
||||
self.handler = handleStream
|
||||
self.codec = DcutrCodec
|
||||
self
|
||||
288
libp2p/protocols/connectivity/relay/client.nim
Normal file
288
libp2p/protocols/connectivity/relay/client.nim
Normal file
@@ -0,0 +1,288 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import times
|
||||
import chronos, chronicles
|
||||
import ./relay,
|
||||
./messages,
|
||||
./rconn,
|
||||
./utils,
|
||||
../../../peerinfo,
|
||||
../../../switch,
|
||||
../../../multiaddress,
|
||||
../../../stream/connection
|
||||
|
||||
logScope:
|
||||
topics = "libp2p relay relay-client"
|
||||
|
||||
const RelayClientMsgSize = 4096
|
||||
|
||||
type
|
||||
RelayClientError* = object of LPError
|
||||
ReservationError* = object of RelayClientError
|
||||
RelayV1DialError* = object of RelayClientError
|
||||
RelayV2DialError* = object of RelayClientError
|
||||
RelayClientAddConn* = proc(conn: Connection,
|
||||
duration: uint32,
|
||||
data: uint64): Future[void] {.gcsafe, raises: [].}
|
||||
RelayClient* = ref object of Relay
|
||||
onNewConnection*: RelayClientAddConn
|
||||
canHop: bool
|
||||
|
||||
Rsvp* = object
|
||||
expire*: uint64 # required, Unix expiration time (UTC)
|
||||
addrs*: seq[MultiAddress] # relay address for reserving peer
|
||||
voucher*: Opt[Voucher] # optional, reservation voucher
|
||||
limitDuration*: uint32 # seconds
|
||||
limitData*: uint64 # bytes
|
||||
|
||||
proc sendStopError(conn: Connection, code: StatusV2) {.async.} =
|
||||
trace "send stop status", status = $code & " (" & $ord(code) & ")"
|
||||
let msg = StopMessage(msgType: StopMessageType.Status, status: Opt.some(code))
|
||||
await conn.writeLp(encode(msg).buffer)
|
||||
|
||||
proc handleRelayedConnect(cl: RelayClient, conn: Connection, msg: StopMessage) {.async.} =
|
||||
let
|
||||
# TODO: check the go version to see in which way this could fail
|
||||
# it's unclear in the spec
|
||||
src = msg.peer.valueOr:
|
||||
await sendStopError(conn, MalformedMessage)
|
||||
return
|
||||
limitDuration = msg.limit.duration
|
||||
limitData = msg.limit.data
|
||||
msg = StopMessage(
|
||||
msgType: StopMessageType.Status,
|
||||
status: Opt.some(Ok))
|
||||
pb = encode(msg)
|
||||
|
||||
trace "incoming relay connection", src
|
||||
|
||||
if cl.onNewConnection == nil:
|
||||
await sendStopError(conn, StatusV2.ConnectionFailed)
|
||||
await conn.close()
|
||||
return
|
||||
await conn.writeLp(pb.buffer)
|
||||
# This sound redundant but the callback could, in theory, be set to nil during
|
||||
# conn.writeLp so it's safer to double check
|
||||
if cl.onNewConnection != nil: await cl.onNewConnection(conn, limitDuration, limitData)
|
||||
else: await conn.close()
|
||||
|
||||
proc reserve*(cl: RelayClient,
|
||||
peerId: PeerId,
|
||||
addrs: seq[MultiAddress] = @[]): Future[Rsvp] {.async.} =
|
||||
let conn = await cl.switch.dial(peerId, addrs, RelayV2HopCodec)
|
||||
defer: await conn.close()
|
||||
let
|
||||
pb = encode(HopMessage(msgType: HopMessageType.Reserve))
|
||||
msg = try:
|
||||
await conn.writeLp(pb.buffer)
|
||||
HopMessage.decode(await conn.readLp(RelayClientMsgSize)).tryGet()
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
trace "error writing or reading reservation message", exc=exc.msg
|
||||
raise newException(ReservationError, exc.msg)
|
||||
|
||||
if msg.msgType != HopMessageType.Status:
|
||||
raise newException(ReservationError, "Unexpected relay response type")
|
||||
if msg.status.get(UnexpectedMessage) != Ok:
|
||||
raise newException(ReservationError, "Reservation failed")
|
||||
|
||||
let reservation = msg.reservation.valueOr:
|
||||
raise newException(ReservationError, "Missing reservation information")
|
||||
if reservation.expire > int64.high().uint64 or
|
||||
now().utc > reservation.expire.int64.fromUnix.utc:
|
||||
raise newException(ReservationError, "Bad expiration date")
|
||||
result.expire = reservation.expire
|
||||
result.addrs = reservation.addrs
|
||||
|
||||
reservation.svoucher.withValue(sv):
|
||||
let svoucher = SignedVoucher.decode(sv).valueOr:
|
||||
raise newException(ReservationError, "Invalid voucher")
|
||||
if svoucher.data.relayPeerId != peerId:
|
||||
raise newException(ReservationError, "Invalid voucher PeerId")
|
||||
result.voucher = Opt.some(svoucher.data)
|
||||
|
||||
result.limitDuration = msg.limit.duration
|
||||
result.limitData = msg.limit.data
|
||||
|
||||
proc dialPeerV1*(
|
||||
cl: RelayClient,
|
||||
conn: Connection,
|
||||
dstPeerId: PeerId,
|
||||
dstAddrs: seq[MultiAddress]): Future[Connection] {.async.} =
|
||||
var
|
||||
msg = RelayMessage(
|
||||
msgType: Opt.some(RelayType.Hop),
|
||||
srcPeer: Opt.some(RelayPeer(peerId: cl.switch.peerInfo.peerId, addrs: cl.switch.peerInfo.addrs)),
|
||||
dstPeer: Opt.some(RelayPeer(peerId: dstPeerId, addrs: dstAddrs)))
|
||||
pb = encode(msg)
|
||||
|
||||
trace "Dial peer", msgSend=msg
|
||||
|
||||
try:
|
||||
await conn.writeLp(pb.buffer)
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
trace "error writing hop request", exc=exc.msg
|
||||
raise exc
|
||||
|
||||
let msgRcvFromRelayOpt = try:
|
||||
RelayMessage.decode(await conn.readLp(RelayClientMsgSize))
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
trace "error reading stop response", exc=exc.msg
|
||||
await sendStatus(conn, StatusV1.HopCantOpenDstStream)
|
||||
raise exc
|
||||
|
||||
try:
|
||||
let msgRcvFromRelay = msgRcvFromRelayOpt.valueOr:
|
||||
raise newException(RelayV1DialError, "Hop can't open destination stream")
|
||||
if msgRcvFromRelay.msgType.tryGet() != RelayType.Status:
|
||||
raise newException(RelayV1DialError, "Hop can't open destination stream: wrong message type")
|
||||
if msgRcvFromRelay.status.tryGet() != StatusV1.Success:
|
||||
raise newException(RelayV1DialError, "Hop can't open destination stream: status failed")
|
||||
except RelayV1DialError as exc:
|
||||
await sendStatus(conn, StatusV1.HopCantOpenDstStream)
|
||||
raise exc
|
||||
except ValueError as exc:
|
||||
await sendStatus(conn, StatusV1.HopCantOpenDstStream)
|
||||
raise newException(RelayV1DialError, exc.msg)
|
||||
result = conn
|
||||
|
||||
proc dialPeerV2*(
|
||||
cl: RelayClient,
|
||||
conn: RelayConnection,
|
||||
dstPeerId: PeerId,
|
||||
dstAddrs: seq[MultiAddress]): Future[Connection] {.async.} =
|
||||
let
|
||||
p = Peer(peerId: dstPeerId, addrs: dstAddrs)
|
||||
pb = encode(HopMessage(msgType: HopMessageType.Connect, peer: Opt.some(p)))
|
||||
|
||||
trace "Dial peer", p
|
||||
|
||||
let msgRcvFromRelay = try:
|
||||
await conn.writeLp(pb.buffer)
|
||||
HopMessage.decode(await conn.readLp(RelayClientMsgSize)).tryGet()
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
trace "error reading stop response", exc=exc.msg
|
||||
raise newException(RelayV2DialError, exc.msg)
|
||||
|
||||
if msgRcvFromRelay.msgType != HopMessageType.Status:
|
||||
raise newException(RelayV2DialError, "Unexpected stop response")
|
||||
if msgRcvFromRelay.status.get(UnexpectedMessage) != Ok:
|
||||
trace "Relay stop failed", msg = msgRcvFromRelay.status
|
||||
raise newException(RelayV2DialError, "Relay stop failure")
|
||||
conn.limitDuration = msgRcvFromRelay.limit.duration
|
||||
conn.limitData = msgRcvFromRelay.limit.data
|
||||
return conn
|
||||
|
||||
proc handleStopStreamV2(cl: RelayClient, conn: Connection) {.async.} =
|
||||
let msg = StopMessage.decode(await conn.readLp(RelayClientMsgSize)).valueOr:
|
||||
await sendHopStatus(conn, MalformedMessage)
|
||||
return
|
||||
trace "client circuit relay v2 handle stream", msg
|
||||
|
||||
if msg.msgType == StopMessageType.Connect:
|
||||
await cl.handleRelayedConnect(conn, msg)
|
||||
else:
|
||||
trace "Unexpected client / relayv2 handshake", msgType=msg.msgType
|
||||
await sendStopError(conn, MalformedMessage)
|
||||
|
||||
proc handleStop(cl: RelayClient, conn: Connection, msg: RelayMessage) {.async.} =
|
||||
let src = msg.srcPeer.valueOr:
|
||||
await sendStatus(conn, StatusV1.StopSrcMultiaddrInvalid)
|
||||
return
|
||||
|
||||
let dst = msg.dstPeer.valueOr:
|
||||
await sendStatus(conn, StatusV1.StopDstMultiaddrInvalid)
|
||||
return
|
||||
|
||||
if dst.peerId != cl.switch.peerInfo.peerId:
|
||||
await sendStatus(conn, StatusV1.StopDstMultiaddrInvalid)
|
||||
return
|
||||
|
||||
trace "get a relay connection", src, conn
|
||||
|
||||
if cl.onNewConnection == nil:
|
||||
await sendStatus(conn, StatusV1.StopRelayRefused)
|
||||
await conn.close()
|
||||
return
|
||||
await sendStatus(conn, StatusV1.Success)
|
||||
# This sound redundant but the callback could, in theory, be set to nil during
|
||||
# sendStatus(Success) so it's safer to double check
|
||||
if cl.onNewConnection != nil: await cl.onNewConnection(conn, 0, 0)
|
||||
else: await conn.close()
|
||||
|
||||
proc handleStreamV1(cl: RelayClient, conn: Connection) {.async.} =
|
||||
let msg = RelayMessage.decode(await conn.readLp(RelayClientMsgSize)).valueOr:
|
||||
await sendStatus(conn, StatusV1.MalformedMessage)
|
||||
return
|
||||
trace "client circuit relay v1 handle stream", msg
|
||||
|
||||
let typ = msg.msgType.valueOr:
|
||||
trace "Message type not set"
|
||||
await sendStatus(conn, StatusV1.MalformedMessage)
|
||||
return
|
||||
case typ:
|
||||
of RelayType.Hop:
|
||||
if cl.canHop: await cl.handleHop(conn, msg)
|
||||
else: await sendStatus(conn, StatusV1.HopCantSpeakRelay)
|
||||
of RelayType.Stop: await cl.handleStop(conn, msg)
|
||||
of RelayType.CanHop:
|
||||
if cl.canHop: await sendStatus(conn, StatusV1.Success)
|
||||
else: await sendStatus(conn, StatusV1.HopCantSpeakRelay)
|
||||
else:
|
||||
trace "Unexpected relay handshake", msgType=msg.msgType
|
||||
await sendStatus(conn, StatusV1.MalformedMessage)
|
||||
|
||||
proc new*(T: typedesc[RelayClient], canHop: bool = false,
|
||||
reservationTTL: times.Duration = DefaultReservationTTL,
|
||||
limitDuration: uint32 = DefaultLimitDuration,
|
||||
limitData: uint64 = DefaultLimitData,
|
||||
heartbeatSleepTime: uint32 = DefaultHeartbeatSleepTime,
|
||||
maxCircuit: int = MaxCircuit,
|
||||
maxCircuitPerPeer: int = MaxCircuitPerPeer,
|
||||
msgSize: int = RelayClientMsgSize,
|
||||
circuitRelayV1: bool = false): T =
|
||||
|
||||
let cl = T(canHop: canHop,
|
||||
reservationTTL: reservationTTL,
|
||||
limit: Limit(duration: limitDuration, data: limitData),
|
||||
heartbeatSleepTime: heartbeatSleepTime,
|
||||
maxCircuit: maxCircuit,
|
||||
maxCircuitPerPeer: maxCircuitPerPeer,
|
||||
msgSize: msgSize,
|
||||
isCircuitRelayV1: circuitRelayV1)
|
||||
proc handleStream(conn: Connection, proto: string) {.async.} =
|
||||
try:
|
||||
case proto:
|
||||
of RelayV1Codec: await cl.handleStreamV1(conn)
|
||||
of RelayV2StopCodec: await cl.handleStopStreamV2(conn)
|
||||
of RelayV2HopCodec: await cl.handleHopStreamV2(conn)
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except CatchableError as exc:
|
||||
trace "exception in client handler", exc = exc.msg, conn
|
||||
finally:
|
||||
trace "exiting client handler", conn
|
||||
await conn.close()
|
||||
|
||||
cl.handler = handleStream
|
||||
cl.codecs = if cl.canHop:
|
||||
@[RelayV1Codec, RelayV2HopCodec, RelayV2StopCodec]
|
||||
else:
|
||||
@[RelayV1Codec, RelayV2StopCodec]
|
||||
cl
|
||||
333
libp2p/protocols/connectivity/relay/messages.nim
Normal file
333
libp2p/protocols/connectivity/relay/messages.nim
Normal file
@@ -0,0 +1,333 @@
|
||||
# Nim-LibP2P
|
||||
# Copyright (c) 2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import macros
|
||||
import stew/[objects, results]
|
||||
import ../../../peerinfo,
|
||||
../../../signed_envelope
|
||||
|
||||
# Circuit Relay V1 Message
|
||||
|
||||
type
|
||||
RelayType* {.pure.} = enum
|
||||
Hop = 1
|
||||
Stop = 2
|
||||
Status = 3
|
||||
CanHop = 4
|
||||
|
||||
StatusV1* {.pure.} = enum
|
||||
Success = 100
|
||||
HopSrcAddrTooLong = 220
|
||||
HopDstAddrTooLong = 221
|
||||
HopSrcMultiaddrInvalid = 250
|
||||
HopDstMultiaddrInvalid = 251
|
||||
HopNoConnToDst = 260
|
||||
HopCantDialDst = 261
|
||||
HopCantOpenDstStream = 262
|
||||
HopCantSpeakRelay = 270
|
||||
HopCantRelayToSelf = 280
|
||||
StopSrcAddrTooLong = 320
|
||||
StopDstAddrTooLong = 321
|
||||
StopSrcMultiaddrInvalid = 350
|
||||
StopDstMultiaddrInvalid = 351
|
||||
StopRelayRefused = 390
|
||||
MalformedMessage = 400
|
||||
|
||||
RelayPeer* = object
|
||||
peerId*: PeerId
|
||||
addrs*: seq[MultiAddress]
|
||||
|
||||
RelayMessage* = object
|
||||
msgType*: Opt[RelayType]
|
||||
srcPeer*: Opt[RelayPeer]
|
||||
dstPeer*: Opt[RelayPeer]
|
||||
status*: Opt[StatusV1]
|
||||
|
||||
proc encode*(msg: RelayMessage): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
|
||||
msg.msgType.withValue(typ):
|
||||
result.write(1, typ.ord.uint)
|
||||
msg.srcPeer.withValue(srcPeer):
|
||||
var peer = initProtoBuffer()
|
||||
peer.write(1, srcPeer.peerId)
|
||||
for ma in srcPeer.addrs:
|
||||
peer.write(2, ma.data.buffer)
|
||||
peer.finish()
|
||||
result.write(2, peer.buffer)
|
||||
msg.dstPeer.withValue(dstPeer):
|
||||
var peer = initProtoBuffer()
|
||||
peer.write(1, dstPeer.peerId)
|
||||
for ma in dstPeer.addrs:
|
||||
peer.write(2, ma.data.buffer)
|
||||
peer.finish()
|
||||
result.write(3, peer.buffer)
|
||||
msg.status.withValue(status):
|
||||
result.write(4, status.ord.uint)
|
||||
|
||||
result.finish()
|
||||
|
||||
proc decode*(_: typedesc[RelayMessage], buf: seq[byte]): Opt[RelayMessage] =
|
||||
var
|
||||
rMsg: RelayMessage
|
||||
msgTypeOrd: uint32
|
||||
src: RelayPeer
|
||||
dst: RelayPeer
|
||||
statusOrd: uint32
|
||||
pbSrc: ProtoBuffer
|
||||
pbDst: ProtoBuffer
|
||||
|
||||
let pb = initProtoBuffer(buf)
|
||||
|
||||
if ? pb.getField(1, msgTypeOrd).toOpt():
|
||||
if msgTypeOrd.int notin RelayType:
|
||||
return Opt.none(RelayMessage)
|
||||
rMsg.msgType = Opt.some(RelayType(msgTypeOrd))
|
||||
|
||||
if ? pb.getField(2, pbSrc).toOpt():
|
||||
discard ? pbSrc.getField(1, src.peerId).toOpt()
|
||||
discard ? pbSrc.getRepeatedField(2, src.addrs).toOpt()
|
||||
rMsg.srcPeer = Opt.some(src)
|
||||
|
||||
if ? pb.getField(3, pbDst).toOpt():
|
||||
discard ? pbDst.getField(1, dst.peerId).toOpt()
|
||||
discard ? pbDst.getRepeatedField(2, dst.addrs).toOpt()
|
||||
rMsg.dstPeer = Opt.some(dst)
|
||||
|
||||
if ? pb.getField(4, statusOrd).toOpt():
|
||||
var status: StatusV1
|
||||
if not checkedEnumAssign(status, statusOrd):
|
||||
return Opt.none(RelayMessage)
|
||||
rMsg.status = Opt.some(status)
|
||||
Opt.some(rMsg)
|
||||
|
||||
# Voucher
|
||||
|
||||
type
|
||||
Voucher* = object
|
||||
relayPeerId*: PeerId # peer ID of the relay
|
||||
reservingPeerId*: PeerId # peer ID of the reserving peer
|
||||
expiration*: uint64 # UNIX UTC expiration time for the reservation
|
||||
|
||||
proc decode*(_: typedesc[Voucher], buf: seq[byte]): Result[Voucher, ProtoError] =
|
||||
let pb = initProtoBuffer(buf)
|
||||
var v = Voucher()
|
||||
|
||||
? pb.getRequiredField(1, v.relayPeerId)
|
||||
? pb.getRequiredField(2, v.reservingPeerId)
|
||||
? pb.getRequiredField(3, v.expiration)
|
||||
|
||||
ok(v)
|
||||
|
||||
proc encode*(v: Voucher): seq[byte] =
|
||||
var pb = initProtoBuffer()
|
||||
|
||||
pb.write(1, v.relayPeerId)
|
||||
pb.write(2, v.reservingPeerId)
|
||||
pb.write(3, v.expiration)
|
||||
|
||||
pb.finish()
|
||||
pb.buffer
|
||||
|
||||
proc init*(T: typedesc[Voucher],
|
||||
relayPeerId: PeerId,
|
||||
reservingPeerId: PeerId,
|
||||
expiration: uint64): T =
|
||||
T(
|
||||
relayPeerId = relayPeerId,
|
||||
reservingPeerId = reservingPeerId,
|
||||
expiration: expiration
|
||||
)
|
||||
|
||||
type SignedVoucher* = SignedPayload[Voucher]
|
||||
|
||||
proc payloadDomain*(_: typedesc[Voucher]): string = "libp2p-relay-rsvp"
|
||||
proc payloadType*(_: typedesc[Voucher]): seq[byte] = @[ (byte)0x03, (byte)0x02 ]
|
||||
|
||||
proc checkValid*(spr: SignedVoucher): Result[void, EnvelopeError] =
|
||||
if not spr.data.relayPeerId.match(spr.envelope.publicKey):
|
||||
err(EnvelopeInvalidSignature)
|
||||
else:
|
||||
ok()
|
||||
|
||||
# Circuit Relay V2 Hop Message
|
||||
|
||||
type
|
||||
Peer* = object
|
||||
peerId*: PeerId
|
||||
addrs*: seq[MultiAddress]
|
||||
Reservation* = object
|
||||
expire*: uint64 # required, Unix expiration time (UTC)
|
||||
addrs*: seq[MultiAddress] # relay address for reserving peer
|
||||
svoucher*: Opt[seq[byte]] # optional, reservation voucher
|
||||
Limit* = object
|
||||
duration*: uint32 # seconds
|
||||
data*: uint64 # bytes
|
||||
|
||||
StatusV2* = enum
|
||||
Ok = 100
|
||||
ReservationRefused = 200
|
||||
ResourceLimitExceeded = 201
|
||||
PermissionDenied = 202
|
||||
ConnectionFailed = 203
|
||||
NoReservation = 204
|
||||
MalformedMessage = 400
|
||||
UnexpectedMessage = 401
|
||||
HopMessageType* {.pure.} = enum
|
||||
Reserve = 0
|
||||
Connect = 1
|
||||
Status = 2
|
||||
HopMessage* = object
|
||||
msgType*: HopMessageType
|
||||
peer*: Opt[Peer]
|
||||
reservation*: Opt[Reservation]
|
||||
limit*: Limit
|
||||
status*: Opt[StatusV2]
|
||||
|
||||
proc encode*(msg: HopMessage): ProtoBuffer =
|
||||
var pb = initProtoBuffer()
|
||||
|
||||
pb.write(1, msg.msgType.ord.uint)
|
||||
msg.peer.withValue(peer):
|
||||
var ppb = initProtoBuffer()
|
||||
ppb.write(1, peer.peerId)
|
||||
for ma in peer.addrs:
|
||||
ppb.write(2, ma.data.buffer)
|
||||
ppb.finish()
|
||||
pb.write(2, ppb.buffer)
|
||||
msg.reservation.withValue(rsrv):
|
||||
var rpb = initProtoBuffer()
|
||||
rpb.write(1, rsrv.expire)
|
||||
for ma in rsrv.addrs:
|
||||
rpb.write(2, ma.data.buffer)
|
||||
rsrv.svoucher.withValue(vouch):
|
||||
rpb.write(3, vouch)
|
||||
rpb.finish()
|
||||
pb.write(3, rpb.buffer)
|
||||
if msg.limit.duration > 0 or msg.limit.data > 0:
|
||||
var lpb = initProtoBuffer()
|
||||
if msg.limit.duration > 0: lpb.write(1, msg.limit.duration)
|
||||
if msg.limit.data > 0: lpb.write(2, msg.limit.data)
|
||||
lpb.finish()
|
||||
pb.write(4, lpb.buffer)
|
||||
msg.status.withValue(status):
|
||||
pb.write(5, status.ord.uint)
|
||||
|
||||
pb.finish()
|
||||
pb
|
||||
|
||||
proc decode*(_: typedesc[HopMessage], buf: seq[byte]): Opt[HopMessage] =
|
||||
var msg: HopMessage
|
||||
let pb = initProtoBuffer(buf)
|
||||
|
||||
var msgTypeOrd: uint32
|
||||
? pb.getRequiredField(1, msgTypeOrd).toOpt()
|
||||
if not checkedEnumAssign(msg.msgType, msgTypeOrd):
|
||||
return Opt.none(HopMessage)
|
||||
|
||||
var pbPeer: ProtoBuffer
|
||||
if ? pb.getField(2, pbPeer).toOpt():
|
||||
var peer: Peer
|
||||
? pbPeer.getRequiredField(1, peer.peerId).toOpt()
|
||||
discard ? pbPeer.getRepeatedField(2, peer.addrs).toOpt()
|
||||
msg.peer = Opt.some(peer)
|
||||
|
||||
var pbReservation: ProtoBuffer
|
||||
if ? pb.getField(3, pbReservation).toOpt():
|
||||
var
|
||||
svoucher: seq[byte]
|
||||
reservation: Reservation
|
||||
if ? pbReservation.getField(3, svoucher).toOpt():
|
||||
reservation.svoucher = Opt.some(svoucher)
|
||||
? pbReservation.getRequiredField(1, reservation.expire).toOpt()
|
||||
discard ? pbReservation.getRepeatedField(2, reservation.addrs).toOpt()
|
||||
msg.reservation = Opt.some(reservation)
|
||||
|
||||
var pbLimit: ProtoBuffer
|
||||
if ? pb.getField(4, pbLimit).toOpt():
|
||||
discard ? pbLimit.getField(1, msg.limit.duration).toOpt()
|
||||
discard ? pbLimit.getField(2, msg.limit.data).toOpt()
|
||||
|
||||
var statusOrd: uint32
|
||||
if ? pb.getField(5, statusOrd).toOpt():
|
||||
var status: StatusV2
|
||||
if not checkedEnumAssign(status, statusOrd):
|
||||
return Opt.none(HopMessage)
|
||||
msg.status = Opt.some(status)
|
||||
Opt.some(msg)
|
||||
|
||||
# Circuit Relay V2 Stop Message
|
||||
|
||||
type
|
||||
StopMessageType* {.pure.} = enum
|
||||
Connect = 0
|
||||
Status = 1
|
||||
StopMessage* = object
|
||||
msgType*: StopMessageType
|
||||
peer*: Opt[Peer]
|
||||
limit*: Limit
|
||||
status*: Opt[StatusV2]
|
||||
|
||||
|
||||
proc encode*(msg: StopMessage): ProtoBuffer =
|
||||
var pb = initProtoBuffer()
|
||||
|
||||
pb.write(1, msg.msgType.ord.uint)
|
||||
msg.peer.withValue(peer):
|
||||
var ppb = initProtoBuffer()
|
||||
ppb.write(1, peer.peerId)
|
||||
for ma in peer.addrs:
|
||||
ppb.write(2, ma.data.buffer)
|
||||
ppb.finish()
|
||||
pb.write(2, ppb.buffer)
|
||||
if msg.limit.duration > 0 or msg.limit.data > 0:
|
||||
var lpb = initProtoBuffer()
|
||||
if msg.limit.duration > 0: lpb.write(1, msg.limit.duration)
|
||||
if msg.limit.data > 0: lpb.write(2, msg.limit.data)
|
||||
lpb.finish()
|
||||
pb.write(3, lpb.buffer)
|
||||
msg.status.withValue(status):
|
||||
pb.write(4, status.ord.uint)
|
||||
|
||||
pb.finish()
|
||||
pb
|
||||
|
||||
proc decode*(_: typedesc[StopMessage], buf: seq[byte]): Opt[StopMessage] =
|
||||
var msg: StopMessage
|
||||
|
||||
let pb = initProtoBuffer(buf)
|
||||
|
||||
var msgTypeOrd: uint32
|
||||
? pb.getRequiredField(1, msgTypeOrd).toOpt()
|
||||
if msgTypeOrd.int notin StopMessageType:
|
||||
return Opt.none(StopMessage)
|
||||
msg.msgType = StopMessageType(msgTypeOrd)
|
||||
|
||||
|
||||
var pbPeer: ProtoBuffer
|
||||
if ? pb.getField(2, pbPeer).toOpt():
|
||||
var peer: Peer
|
||||
? pbPeer.getRequiredField(1, peer.peerId).toOpt()
|
||||
discard ? pbPeer.getRepeatedField(2, peer.addrs).toOpt()
|
||||
msg.peer = Opt.some(peer)
|
||||
|
||||
var pbLimit: ProtoBuffer
|
||||
if ? pb.getField(3, pbLimit).toOpt():
|
||||
discard ? pbLimit.getField(1, msg.limit.duration).toOpt()
|
||||
discard ? pbLimit.getField(2, msg.limit.data).toOpt()
|
||||
|
||||
var statusOrd: uint32
|
||||
if ? pb.getField(4, statusOrd).toOpt():
|
||||
var status: StatusV2
|
||||
if not checkedEnumAssign(status, statusOrd):
|
||||
return Opt.none(StopMessage)
|
||||
msg.status = Opt.some(status)
|
||||
Opt.some(msg)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user