zanzibar
1 year ago
108 changed files with 8210 additions and 2169 deletions
@ -1,6 +1,11 @@ |
|||||
185.25.48.236:27485 |
# node1.hush.land |
||||
185.25.48.236:27487 |
185.241.61.43 |
||||
185.64.105.111:27485 |
|
||||
185.64.105.111:27487 |
# node2.hush.is |
||||
185.25.48.72:27485 |
137.74.4.198 |
||||
185.25.48.72:27487 |
|
||||
|
# lite.hushpool.is |
||||
|
149.28.102.219 |
||||
|
|
||||
|
# todo: torv3 |
||||
|
# todo: i2p |
||||
|
@ -0,0 +1,161 @@ |
|||||
|
# I2P support in Hush |
||||
|
|
||||
|
It is possible to run a Hush or HSC full node as an |
||||
|
[I2P (Invisible Internet Project)](https://en.wikipedia.org/wiki/I2P) |
||||
|
service and connect to such services. |
||||
|
|
||||
|
This [glossary](https://geti2p.net/en/about/glossary) may be useful to get |
||||
|
started with I2P terminology. |
||||
|
|
||||
|
## Run with an I2P router (proxy) |
||||
|
|
||||
|
A running I2P router (proxy) with [SAM](https://geti2p.net/en/docs/api/samv3) |
||||
|
enabled is required. Options include: |
||||
|
|
||||
|
- [i2prouter (I2P Router)](https://geti2p.net), the official implementation in |
||||
|
Java |
||||
|
- [i2pd (I2P Daemon)](https://github.com/PurpleI2P/i2pd) |
||||
|
([documentation](https://i2pd.readthedocs.io/en/latest)), a lighter |
||||
|
alternative in C++ (successfully tested with version 2.23 and up; version 2.36 |
||||
|
or later recommended) |
||||
|
- [i2p-zero](https://github.com/i2p-zero/i2p-zero) |
||||
|
- [other alternatives](https://en.wikipedia.org/wiki/I2P#Routers) |
||||
|
|
||||
|
Note the IP address and port the SAM proxy is listening to; usually, it is |
||||
|
`127.0.0.1:7656`. |
||||
|
|
||||
|
Once an I2P router with SAM enabled is up and running, use the following |
||||
|
configuration options: |
||||
|
|
||||
|
``` |
||||
|
-i2psam=<ip:port> |
||||
|
I2P SAM proxy to reach I2P peers and accept I2P connections (default: |
||||
|
none) |
||||
|
|
||||
|
-i2pacceptincoming |
||||
|
If set and -i2psam is also set then incoming I2P connections are |
||||
|
accepted via the SAM proxy. If this is not set but -i2psam is set |
||||
|
then only outgoing connections will be made to the I2P network. |
||||
|
Ignored if -i2psam is not set. Listening for incoming I2P |
||||
|
connections is done through the SAM proxy, not by binding to a |
||||
|
local address and port (default: 1) |
||||
|
``` |
||||
|
|
||||
|
In a typical situation, this suffices: |
||||
|
|
||||
|
``` |
||||
|
hushd -i2psam=127.0.0.1:7656 |
||||
|
``` |
||||
|
|
||||
|
The first time hushd connects to the I2P router, if |
||||
|
`-i2pacceptincoming=1`, then it will automatically generate a persistent I2P |
||||
|
address and its corresponding private key. The private key will be saved in a |
||||
|
file named `i2p_private_key` in the Hush data directory. The persistent |
||||
|
I2P address is used for accepting incoming connections and for making outgoing |
||||
|
connections if `-i2pacceptincoming=1`. If `-i2pacceptincoming=0` then only |
||||
|
outbound I2P connections are made and a different transient I2P address is used |
||||
|
for each connection to improve privacy. |
||||
|
|
||||
|
## Persistent vs transient I2P addresses |
||||
|
|
||||
|
In I2P connections, the connection receiver sees the I2P address of the |
||||
|
connection initiator. This is unlike the Tor network where the recipient does |
||||
|
not know who is connecting to them and can't tell if two connections are from |
||||
|
the same peer or not. |
||||
|
|
||||
|
If an I2P node is not accepting incoming connections, then Hush uses |
||||
|
random, one-time, transient I2P addresses for itself for outbound connections |
||||
|
to make it harder to discriminate, fingerprint or analyze it based on its I2P |
||||
|
address. |
||||
|
|
||||
|
## Additional configuration options related to I2P |
||||
|
|
||||
|
``` |
||||
|
-debug=i2p |
||||
|
``` |
||||
|
|
||||
|
Set the `debug=i2p` config logging option to see additional information in the |
||||
|
debug log about your I2P configuration and connections. Run `hush-cli help |
||||
|
logging` for more information. |
||||
|
|
||||
|
``` |
||||
|
-onlynet=i2p |
||||
|
``` |
||||
|
|
||||
|
Make automatic outbound connections only to I2P addresses. Inbound and manual |
||||
|
connections are not affected by this option. It can be specified multiple times |
||||
|
to allow multiple networks, e.g. onlynet=onion, onlynet=i2p. |
||||
|
|
||||
|
I2P support was added to Hush in version 3.9.3 and there may be fewer I2P |
||||
|
peers than Tor or IP ones. Therefore, using I2P alone without other networks may |
||||
|
make a node more susceptible to [Sybil |
||||
|
attacks](https://en.bitcoin.it/wiki/Weaknesses#Sybil_attack). You can use |
||||
|
`hush-cli -addrinfo` to see the number of I2P addresses known to your node. |
||||
|
|
||||
|
Another consideration with `onlynet=i2p` is that the initial blocks download |
||||
|
phase when syncing up a new node can be very slow. This phase can be sped up by |
||||
|
using other networks, for instance `onlynet=onion`, at the same time. |
||||
|
|
||||
|
In general, a node can be run with both onion and I2P hidden services (or |
||||
|
any/all of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if |
||||
|
one of the networks has issues. |
||||
|
|
||||
|
## I2P-related information |
||||
|
|
||||
|
There are several ways to see your I2P address if accepting |
||||
|
incoming I2P connections (`-i2pacceptincoming`): |
||||
|
- in the "Local addresses" output of CLI `-netinfo` |
||||
|
- in the "localaddresses" output of RPC `getnetworkinfo` |
||||
|
- in the debug log (grep for `AddLocal`; the I2P address ends in `.b32.i2p`) |
||||
|
|
||||
|
To see which I2P peers your node is connected to, use `hush-cli -netinfo 4` |
||||
|
or the `getpeerinfo` RPC (e.g. `hush-cli getpeerinfo`). |
||||
|
|
||||
|
To see which I2P addresses your node knows, use the `getnodeaddresses 0 i2p` |
||||
|
RPC. |
||||
|
|
||||
|
## Compatibility |
||||
|
|
||||
|
Hush uses the [SAM v3.1](https://geti2p.net/en/docs/api/samv3) protocol |
||||
|
to connect to the I2P network. Any I2P router that supports it can be used. |
||||
|
|
||||
|
## Ports in I2P and Hush |
||||
|
|
||||
|
Hush uses the [SAM v3.1](https://geti2p.net/en/docs/api/samv3) |
||||
|
protocol. One particularity of SAM v3.1 is that it does not support ports, |
||||
|
unlike newer versions of SAM (v3.2 and up) that do support them and default the |
||||
|
port numbers to 0. From the point of view of peers that use newer versions of |
||||
|
SAM or other protocols that support ports, a SAM v3.1 peer is connecting to them |
||||
|
on port 0, from source port 0. |
||||
|
|
||||
|
To allow future upgrades to newer versions of SAM, Hush sets its |
||||
|
listening port to 0 when listening for incoming I2P connections and advertises |
||||
|
its own I2P address with port 0. Furthermore, it will not attempt to connect to |
||||
|
I2P addresses with a non-zero port number because with SAM v3.1 the destination |
||||
|
port (`TO_PORT`) is always set to 0 and is not in the control of Hush. |
||||
|
|
||||
|
## Bandwidth |
||||
|
|
||||
|
I2P routers may route a large amount of general network traffic with their |
||||
|
default settings. Check your router's configuration to limit the amount of this |
||||
|
traffic relayed, if desired. |
||||
|
|
||||
|
With `i2pd`, the amount of bandwidth being shared with the wider network can be |
||||
|
adjusted with the `bandwidth`, `share` and `transittunnels` options in your |
||||
|
`i2pd.conf` file. For example, to limit total I2P traffic to 256KB/s and share |
||||
|
50% of this limit for a maximum of 20 transit tunnels: |
||||
|
|
||||
|
``` |
||||
|
bandwidth = 256 |
||||
|
share = 50 |
||||
|
|
||||
|
[limits] |
||||
|
transittunnels = 20 |
||||
|
``` |
||||
|
|
||||
|
If you prefer not to relay any public I2P traffic and only permit I2P traffic |
||||
|
from programs which are connecting via the SAM proxy, e.g. Hush, you |
||||
|
can set the `notransit` option to `true`. |
||||
|
|
||||
|
Similar bandwidth configuration options for the Java I2P router can be found in |
||||
|
`http://127.0.0.1:7657/config` under the "Bandwidth" tab. |
@ -1,154 +1,225 @@ |
|||||
# Warning |
# Tor |
||||
|
|
||||
Do not assume Tor support works perfectly in Hush; better Tor support is currently being worked on. |
It is possible to run Hush as a Tor onion service, and connect to such services. |
||||
|
|
||||
# Hush + Tor |
|
||||
|
|
||||
It is possible to run Hush as a Tor hidden service, and connect to such services. |
|
||||
|
|
||||
The following directions assume you have a Tor proxy running on port 9050. Many distributions default to having a SOCKS proxy listening on port 9050, but others may not. In particular, the Tor Browser Bundle defaults to listening on port 9150. See [Tor Project FAQ:TBBSocksPort](https://www.torproject.org/docs/faq.html.en#TBBSocksPort) for how to properly |
The following directions assume you have a Tor proxy running on port 9050. Many distributions default to having a SOCKS proxy listening on port 9050, but others may not. In particular, the Tor Browser Bundle defaults to listening on port 9150. See [Tor Project FAQ:TBBSocksPort](https://www.torproject.org/docs/faq.html.en#TBBSocksPort) for how to properly |
||||
configure Tor. |
configure Tor. |
||||
|
|
||||
|
## Compatibility |
||||
|
|
||||
|
- Starting with version 3.9.3, Hush only supports Tor version 3 hidden |
||||
|
services (Tor v3). Tor v2 addresses are ignored by Hush and neither |
||||
|
relayed nor stored. |
||||
|
|
||||
|
- Tor removed v2 support beginning with version 0.4.6. |
||||
|
|
||||
|
## How to see information about your Tor configuration via Hush |
||||
|
|
||||
|
There are several ways to see your local onion address in Hush: |
||||
|
- in the "Local addresses" output of CLI `-netinfo` |
||||
|
- in the "localaddresses" output of RPC `getnetworkinfo` |
||||
|
- in the debug log (grep for "AddLocal"; the Tor address ends in `.onion`) |
||||
|
|
||||
|
You may set the `-debug=tor` config logging option to have additional |
||||
|
information in the debug log about your Tor configuration. |
||||
|
|
||||
|
CLI `-addrinfo` returns the number of addresses known to your node per |
||||
|
network. This can be useful to see how many onion peers your node knows, |
||||
|
e.g. for `-onlynet=onion`. |
||||
|
|
||||
1. Run Hush behind a Tor proxy |
To fetch a number of onion addresses that your node knows, for example seven |
||||
------------------------------- |
addresses, use the `getnodeaddresses 7 onion` RPC. |
||||
|
|
||||
The first step is running Hush behind a Tor proxy. This will already make all |
## 1. Run Hush behind a Tor proxy |
||||
outgoing connections be anonymized, but more is possible. |
|
||||
|
|
||||
-proxy=ip:port Set the proxy server. If SOCKS5 is selected (default), this proxy |
The first step is running Hush behind a Tor proxy. This will already anonymize all |
||||
server will be used to try to reach .onion addresses as well. |
outgoing connections, but more is possible. |
||||
|
|
||||
-onion=ip:port Set the proxy server to use for Tor hidden services. You do not |
-proxy=ip:port Set the proxy server. If SOCKS5 is selected (default), this proxy |
||||
need to set this if it's the same as -proxy. You can use -noonion |
server will be used to try to reach .onion addresses as well. |
||||
to explicitly disable access to hidden service. |
You need to use -noonion or -onion=0 to explicitly disable |
||||
|
outbound access to onion services. |
||||
|
|
||||
-listen When using -proxy, listening is disabled by default. If you want |
-onion=ip:port Set the proxy server to use for Tor onion services. You do not |
||||
to run a hidden service (see next section), you'll need to enable |
need to set this if it's the same as -proxy. You can use -onion=0 |
||||
it explicitly. |
to explicitly disable access to onion services. |
||||
|
------------------------------------------------------------------ |
||||
|
Note: Only the -proxy option sets the proxy for DNS requests; |
||||
|
with -onion they will not route over Tor, so use -proxy if you |
||||
|
have privacy concerns. |
||||
|
------------------------------------------------------------------ |
||||
|
|
||||
-connect=X When behind a Tor proxy, you can specify .onion addresses instead |
-listen When using -proxy, listening is disabled by default. If you want |
||||
-addnode=X of IP addresses or hostnames in these parameters. It requires |
to manually configure an onion service (see section 3), you'll |
||||
-seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with |
need to enable it explicitly. |
||||
other P2P nodes. |
|
||||
|
-connect=X When behind a Tor proxy, you can specify .onion addresses instead |
||||
|
-addnode=X of IP addresses or hostnames in these parameters. It requires |
||||
|
-seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with |
||||
|
other P2P nodes. |
||||
|
|
||||
|
-onlynet=onion Make automatic outbound connections only to .onion addresses. |
||||
|
Inbound and manual connections are not affected by this option. |
||||
|
It can be specified multiple times to allow multiple networks, |
||||
|
e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns. |
||||
|
|
||||
In a typical situation, this suffices to run behind a Tor proxy: |
In a typical situation, this suffices to run behind a Tor proxy: |
||||
|
|
||||
./hushd -proxy=127.0.0.1:9050 |
./hushd -proxy=127.0.0.1:9050 |
||||
|
|
||||
If using the Tor Browser Bundle: |
## 2. Automatically create a Hush onion service |
||||
|
|
||||
./hushd -proxy=127.0.0.1:9150 |
Hush makes use of Tor's control socket API to create and destroy |
||||
|
ephemeral onion services programmatically. This means that if Tor is running and |
||||
|
proper authentication has been configured, Hush automatically creates an |
||||
|
onion service to listen on. The goal is to increase the number of available |
||||
|
onion nodes. |
||||
|
|
||||
|
This feature is enabled by default if Hush is listening (`-listen`) and |
||||
|
it requires a Tor connection to work. It can be explicitly disabled with |
||||
|
`-listenonion=0`. If it is not disabled, it can be configured using the |
||||
|
`-torcontrol` and `-torpassword` settings. |
||||
|
|
||||
|
To see verbose Tor information in the hushd debug log, pass `-debug=tor`. |
||||
|
|
||||
2. Run a Hush hidden server |
### Control Port |
||||
---------------------------- |
|
||||
|
|
||||
If you configure your Tor system accordingly, it is possible to make your node also |
You may need to set up the Tor Control Port. On Linux distributions there may be |
||||
reachable from the Tor network. Add these lines to your /etc/tor/torrc (or equivalent |
some or all of the following settings in `/etc/tor/torrc`, generally commented |
||||
config file): |
out by default (if not, add them): |
||||
|
|
||||
HiddenServiceDir /var/lib/tor/hush-service/ |
``` |
||||
HiddenServicePort 18030 127.0.0.1:18030 |
ControlPort 9051 |
||||
|
CookieAuthentication 1 |
||||
|
CookieAuthFileGroupReadable 1 |
||||
|
``` |
||||
|
|
||||
The directory can be different of course, but (both) port numbers should be equal to |
Add or uncomment those, save, and restart Tor (usually `systemctl restart tor` |
||||
your hushd's P2P listen port (18030 by default). |
or `sudo systemctl restart tor` on most systemd-based systems, including recent |
||||
|
Debian and Ubuntu, or just restart the computer). |
||||
|
|
||||
-externalip=X You can tell Hush about its publicly reachable address using |
On some systems (such as Arch Linux), you may also need to add the following |
||||
this option, and this can be a .onion address. Given the above |
line: |
||||
configuration, you can find your onion address in |
|
||||
/var/lib/tor/hush-service/hostname. Onion addresses are given |
|
||||
preference for your node to advertize itself with, for connections |
|
||||
coming from unroutable addresses (such as 127.0.0.1, where the |
|
||||
Tor proxy typically runs). |
|
||||
|
|
||||
-listen You'll need to enable listening for incoming connections, as this |
``` |
||||
is off by default behind a proxy. |
DataDirectoryGroupReadable 1 |
||||
|
``` |
||||
|
|
||||
-discover When -externalip is specified, no attempt is made to discover local |
### Authentication |
||||
IPv4 or IPv6 addresses. If you want to run a dual stack, reachable |
|
||||
from both Tor and IPv4 (or IPv6), you'll need to either pass your |
|
||||
other addresses using -externalip, or explicitly enable -discover. |
|
||||
Note that both addresses of a dual-stack system may be easily |
|
||||
linkable using traffic analysis. |
|
||||
|
|
||||
In a typical situation, where you're only reachable via Tor, this should suffice: |
Connecting to Tor's control socket API requires one of two authentication |
||||
|
methods to be configured: cookie authentication or hushd's `-torpassword` |
||||
|
configuration option. |
||||
|
|
||||
./hushd -proxy=127.0.0.1:9050 -externalip=hushc0de123.onion -listen |
#### Cookie authentication |
||||
|
|
||||
(obviously, replace the Onion address with your own). Currently only v2 HS's are supported. |
For cookie authentication, the user running hushd must have read access to |
||||
It should be noted that you still listen on all devices and another node could establish a clearnet connection, when knowing |
the `CookieAuthFile` specified in the Tor configuration. In some cases this is |
||||
your address. To mitigate this, additionally bind the address of your Tor proxy: |
preconfigured and the creation of an onion service is automatic. Don't forget to |
||||
|
use the `-debug=tor` hushd configuration option to enable Tor debug logging. |
||||
|
|
||||
./hushd ... -bind=127.0.0.1 |
If a permissions problem is seen in the debug log, e.g. `tor: Authentication |
||||
|
cookie /run/tor/control.authcookie could not be opened (check permissions)`, it |
||||
|
can be resolved by adding both the user running Tor and the user running |
||||
|
hushd to the same Tor group and setting permissions appropriately. |
||||
|
|
||||
If you don't care too much about hiding your node, and want to be reachable on IPv4 |
On Debian-derived systems, the Tor group will likely be `debian-tor` and one way |
||||
as well, use `discover` instead: |
to verify could be to list the groups and grep for a "tor" group name: |
||||
|
|
||||
./hushd ... -discover |
``` |
||||
|
getent group | cut -d: -f1 | grep -i tor |
||||
|
``` |
||||
|
|
||||
and open port 18030 on your firewall. |
You can also check the group of the cookie file. On most Linux systems, the Tor |
||||
|
auth cookie will usually be `/run/tor/control.authcookie`: |
||||
|
|
||||
If you only want to use Tor to reach onion addresses, but not use it as a proxy |
``` |
||||
for normal IPv4/IPv6 communication, use: |
TORGROUP=$(stat -c '%G' /run/tor/control.authcookie) |
||||
|
``` |
||||
|
|
||||
./hushd -onion=127.0.0.1:9050 -externalip=hushc0de123.onion -discover |
Once you have determined the `${TORGROUP}` and selected the `${USER}` that will |
||||
|
run hushd, run this as root: |
||||
|
|
||||
|
``` |
||||
|
usermod -a -G ${TORGROUP} ${USER} |
||||
|
``` |
||||
|
|
||||
3. Automatically listen on Tor |
Then restart the computer (or log out) and log in as the `${USER}` that will run |
||||
-------------------------------- |
hushd. |
||||
|
|
||||
Starting with Tor version 0.2.7.1 it is possible, through Tor's control socket |
#### `torpassword` authentication |
||||
API, to create and destroy 'ephemeral' hidden services programmatically. |
|
||||
Hush has been updated to make use of this. |
|
||||
|
|
||||
This means that if Tor is running (and proper authentication has been configured), |
For the `-torpassword=password` option, the password is the clear text form that |
||||
Hush automatically creates a hidden service to listen on. Hush will also use Tor |
was used when generating the hashed password for the `HashedControlPassword` |
||||
automatically to connect to other .onion nodes if the control socket can be |
option in the Tor configuration file. |
||||
successfully opened. This will positively affect the number of available .onion |
|
||||
nodes and their usage. |
|
||||
|
|
||||
This new feature is enabled by default if Hush is listening (`-listen`), and |
The hashed password can be obtained with the command `tor --hash-password |
||||
requires a Tor connection to work. It can be explicitly disabled with `-listenonion=0` |
password` (refer to the [Tor Dev |
||||
and, if not disabled, configured using the `-torcontrol` and `-torpassword` settings. |
Manual](https://2019.www.torproject.org/docs/tor-manual.html.en) for more |
||||
To show verbose debugging information, pass `-debug=tor`. |
details). |
||||
|
|
||||
Connecting to Tor's control socket API requires one of two authentication methods to be |
|
||||
configured. For cookie authentication the user running hushd must have write access |
|
||||
to the `CookieAuthFile` specified in Tor configuration. In some cases this is |
|
||||
preconfigured and the creation of a hidden service is automatic. If permission problems |
|
||||
are seen with `-debug=tor` they can be resolved by adding both the user running tor and |
|
||||
the user running hushd to the same group and setting permissions appropriately. On |
|
||||
Debian-based systems the user running hushd can be added to the debian-tor group, |
|
||||
which has the appropriate permissions. An alternative authentication method is the use |
|
||||
of the `-torpassword` flag and a `hash-password` which can be enabled and specified in |
|
||||
Tor configuration. |
|
||||
|
|
||||
|
## 3. Manually create a Hush onion service |
||||
|
|
||||
4. Connect to a Hush hidden server |
You can also manually configure your node to be reachable from the Tor network. |
||||
----------------------------------- |
Add these lines to your `/etc/tor/torrc` (or equivalent config file): |
||||
|
|
||||
To test your set-up, you might want to try connecting via Tor on a different computer to just a |
HiddenServiceDir /var/lib/tor/hush-service/ |
||||
a single Hush hidden server. Launch hushd as follows: |
HiddenServicePort 18030 127.0.0.1:18032 |
||||
|
|
||||
./hushd -onion=127.0.0.1:9050 -connect=fuckzookoie6wxgio.onion |
The directory can be different of course, but virtual port numbers should be equal to |
||||
|
your hushd's P2P listen port (18030 by default), and target addresses and ports |
||||
|
should be equal to binding address and port for inbound Tor connections (127.0.0.1:18032 by default). |
||||
|
|
||||
Now use hush-cli to verify there is only a single peer connection. |
-externalip=X You can tell hush about its publicly reachable addresses using |
||||
|
this option, and this can be an onion address. Given the above |
||||
|
configuration, you can find your onion address in |
||||
|
/var/lib/tor/hush-service/hostname. For connections |
||||
|
coming from unroutable addresses (such as 127.0.0.1, where the |
||||
|
Tor proxy typically runs), onion addresses are given |
||||
|
preference for your node to advertise itself with. |
||||
|
|
||||
hush-cli getpeerinfo |
You can set multiple local addresses with -externalip. The |
||||
|
one that will be rumoured to a particular peer is the most |
||||
|
compatible one and also using heuristics, e.g. the address |
||||
|
with the most incoming connections, etc. |
||||
|
|
||||
|
-listen You'll need to enable listening for incoming connections, as this |
||||
|
is off by default behind a proxy. |
||||
|
|
||||
|
-discover When -externalip is specified, no attempt is made to discover local |
||||
|
IPv4 or IPv6 addresses. If you want to run a dual stack, reachable |
||||
|
from both Tor and IPv4 (or IPv6), you'll need to either pass your |
||||
|
other addresses using -externalip, or explicitly enable -discover. |
||||
|
Note that both addresses of a dual-stack system may be easily |
||||
|
linkable using traffic analysis. |
||||
|
|
||||
|
In a typical situation, where you're only reachable via Tor, this should suffice: |
||||
|
|
||||
|
./hushd -proxy=127.0.0.1:9050 -externalip=7zvj7a2imdgkdbg4f2dryd5rgtrn7upivr5eeij4cicjh65pooxeshid.onion -listen |
||||
|
|
||||
|
(obviously, replace the .onion address with your own). It should be noted that you still |
||||
|
listen on all devices and another node could establish a clearnet connection, when knowing |
||||
|
your address. To mitigate this, additionally bind the address of your Tor proxy: |
||||
|
|
||||
|
./hushd ... -bind=127.0.0.1 |
||||
|
|
||||
|
If you don't care too much about hiding your node, and want to be reachable on IPv4 |
||||
|
as well, use `discover` instead: |
||||
|
|
||||
|
./hushd ... -discover |
||||
|
|
||||
|
and open port 18030 on your firewall (or use port mapping, i.e., `-upnp` or `-natpmp`). |
||||
|
|
||||
|
If you only want to use Tor to reach .onion addresses, but not use it as a proxy |
||||
|
for normal IPv4/IPv6 communication, use: |
||||
|
|
||||
[ |
./hushd -onion=127.0.0.1:9050 -externalip=7zvj7a2imdgkdbg4f2dryd5rgtrn7upivr5eeij4cicjh65pooxeshid.onion -discover |
||||
{ |
|
||||
"id" : 1, |
|
||||
"addr" : "zcashhoneypot.onion:18030", |
|
||||
... |
|
||||
"version" : 1987420, |
|
||||
"subver" : "/GoldenSandtrout:3.6.0/", |
|
||||
... |
|
||||
} |
|
||||
] |
|
||||
|
|
||||
To connect to multiple Tor nodes, use: |
## 4. Privacy recommendations |
||||
|
|
||||
./hushd -onion=127.0.0.1:9050 -addnode=hushbeef123.onion -dnsseed=0 -onlynet=onion |
- Do not add anything but Hush ports to the onion service created in section 3. |
||||
|
If you run a web service too, create a new onion service for that. |
||||
|
Otherwise it is trivial to link them, which may reduce privacy. Onion |
||||
|
services created automatically (as in section 2) always have only one port |
||||
|
open. |
||||
|
@ -0,0 +1,122 @@ |
|||||
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
|
// Copyright (c) 2009-2016 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#include "addrdb.h" |
||||
|
#include "addrman.h" |
||||
|
#include "chainparams.h" |
||||
|
#include "clientversion.h" |
||||
|
#include "fs.h" |
||||
|
#include "hash.h" |
||||
|
#include "random.h" |
||||
|
#include "streams.h" |
||||
|
#include "tinyformat.h" |
||||
|
#include "util.h" |
||||
|
|
||||
|
namespace { |
||||
|
|
||||
|
template <typename Stream, typename Data> |
||||
|
bool SerializeDB(Stream& stream, const Data& data) |
||||
|
{ |
||||
|
// Write and commit header, data
|
||||
|
try { |
||||
|
CHashWriter hasher(stream.GetType(), stream.GetVersion()); |
||||
|
stream << FLATDATA(Params().MessageStart()) << data; |
||||
|
hasher << FLATDATA(Params().MessageStart()) << data; |
||||
|
stream << hasher.GetHash(); |
||||
|
} catch (const std::exception& e) { |
||||
|
return error("%s: Serialize or I/O error - %s", __func__, e.what()); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
template <typename Data> |
||||
|
bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data) |
||||
|
{ |
||||
|
// Generate random temporary filename
|
||||
|
unsigned short randv = 0; |
||||
|
GetRandBytes((unsigned char*)&randv, sizeof(randv)); |
||||
|
std::string tmpfn = strprintf("%s.%04x", prefix, randv); |
||||
|
|
||||
|
// open temp output file, and associate with CAutoFile
|
||||
|
fs::path pathTmp = GetDataDir() / tmpfn; |
||||
|
FILE *file = fsbridge::fopen(pathTmp, "wb"); |
||||
|
CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); |
||||
|
if (fileout.IsNull()) |
||||
|
return error("%s: Failed to open file %s", __func__, pathTmp.string()); |
||||
|
|
||||
|
// Serialize
|
||||
|
if (!SerializeDB(fileout, data)) return false; |
||||
|
FileCommit(fileout.Get()); |
||||
|
fileout.fclose(); |
||||
|
|
||||
|
// replace existing file, if any, with new file
|
||||
|
if (!RenameOver(pathTmp, path)) |
||||
|
return error("%s: Rename-into-place failed", __func__); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
template <typename Stream, typename Data> |
||||
|
bool DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true) |
||||
|
{ |
||||
|
try { |
||||
|
CHashVerifier<Stream> verifier(&stream); |
||||
|
// de-serialize file header (network specific magic number) and ..
|
||||
|
unsigned char pchMsgTmp[4]; |
||||
|
verifier >> FLATDATA(pchMsgTmp); |
||||
|
// ... verify the network matches ours
|
||||
|
if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) |
||||
|
return error("%s: Invalid network magic number", __func__); |
||||
|
|
||||
|
// de-serialize data
|
||||
|
verifier >> data; |
||||
|
|
||||
|
// verify checksum
|
||||
|
if (fCheckSum) { |
||||
|
uint256 hashTmp; |
||||
|
stream >> hashTmp; |
||||
|
if (hashTmp != verifier.GetHash()) { |
||||
|
return error("%s: Checksum mismatch, data corrupted", __func__); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch (const std::exception& e) { |
||||
|
return error("%s: Deserialize or I/O error - %s", __func__, e.what()); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
template <typename Data> |
||||
|
bool DeserializeFileDB(const fs::path& path, Data& data) |
||||
|
{ |
||||
|
// open input file, and associate with CAutoFile
|
||||
|
FILE *file = fsbridge::fopen(path, "rb"); |
||||
|
CAutoFile filein(file, SER_DISK, CLIENT_VERSION); |
||||
|
if (filein.IsNull()) |
||||
|
return error("%s: Failed to open file %s", __func__, path.string()); |
||||
|
|
||||
|
return DeserializeDB(filein, data); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
CBanDB::CBanDB() |
||||
|
{ |
||||
|
pathBanlist = GetDataDir() / "banlist.dat"; |
||||
|
} |
||||
|
|
||||
|
bool CBanDB::Write(const banmap_t& banSet) |
||||
|
{ |
||||
|
return SerializeFileDB("banlist", pathBanlist, banSet); |
||||
|
} |
||||
|
|
||||
|
bool CBanDB::Read(banmap_t& banSet) |
||||
|
{ |
||||
|
return DeserializeFileDB(pathBanlist, banSet); |
||||
|
} |
||||
|
|
@ -0,0 +1,90 @@ |
|||||
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
|
// Copyright (c) 2009-2016 The Bitcoin Core developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef HUSH_ADDRDB_H |
||||
|
#define HUSH_ADDRDB_H |
||||
|
|
||||
|
#include "fs.h" |
||||
|
#include "serialize.h" |
||||
|
|
||||
|
#include <string> |
||||
|
#include <map> |
||||
|
|
||||
|
class CSubNet; |
||||
|
class CAddrMan; |
||||
|
class CDataStream; |
||||
|
|
||||
|
typedef enum BanReason |
||||
|
{ |
||||
|
BanReasonUnknown = 0, |
||||
|
BanReasonNodeMisbehaving = 1, |
||||
|
BanReasonManuallyAdded = 2 |
||||
|
} BanReason; |
||||
|
|
||||
|
class CBanEntry |
||||
|
{ |
||||
|
public: |
||||
|
static const int CURRENT_VERSION=1; |
||||
|
int nVersion; |
||||
|
int64_t nCreateTime; |
||||
|
int64_t nBanUntil; |
||||
|
uint8_t banReason; |
||||
|
|
||||
|
CBanEntry() |
||||
|
{ |
||||
|
SetNull(); |
||||
|
} |
||||
|
|
||||
|
explicit CBanEntry(int64_t nCreateTimeIn) |
||||
|
{ |
||||
|
SetNull(); |
||||
|
nCreateTime = nCreateTimeIn; |
||||
|
} |
||||
|
|
||||
|
ADD_SERIALIZE_METHODS; |
||||
|
|
||||
|
template <typename Stream, typename Operation> |
||||
|
inline void SerializationOp(Stream& s, Operation ser_action) { |
||||
|
READWRITE(this->nVersion); |
||||
|
READWRITE(nCreateTime); |
||||
|
READWRITE(nBanUntil); |
||||
|
READWRITE(banReason); |
||||
|
} |
||||
|
|
||||
|
void SetNull() |
||||
|
{ |
||||
|
nVersion = CBanEntry::CURRENT_VERSION; |
||||
|
nCreateTime = 0; |
||||
|
nBanUntil = 0; |
||||
|
banReason = BanReasonUnknown; |
||||
|
} |
||||
|
|
||||
|
std::string banReasonToString() const |
||||
|
{ |
||||
|
switch (banReason) { |
||||
|
case BanReasonNodeMisbehaving: |
||||
|
return "node misbehaving"; |
||||
|
case BanReasonManuallyAdded: |
||||
|
return "manually added"; |
||||
|
default: |
||||
|
return "unknown"; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
typedef std::map<CSubNet, CBanEntry> banmap_t; |
||||
|
|
||||
|
/** Access to the banlist database (banlist.dat) */ |
||||
|
class CBanDB |
||||
|
{ |
||||
|
private: |
||||
|
fs::path pathBanlist; |
||||
|
public: |
||||
|
CBanDB(); |
||||
|
bool Write(const banmap_t& banSet); |
||||
|
bool Read(banmap_t& banSet); |
||||
|
}; |
||||
|
|
||||
|
#endif // HUSH_ADDRDB_H
|
@ -0,0 +1,20 @@ |
|||||
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
|
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef HUSH_ATTRIBUTES_H |
||||
|
#define HUSH_ATTRIBUTES_H |
||||
|
|
||||
|
#if defined(__clang__) |
||||
|
# if __has_attribute(lifetimebound) |
||||
|
# define LIFETIMEBOUND [[clang::lifetimebound]] |
||||
|
# else |
||||
|
# define LIFETIMEBOUND |
||||
|
# endif |
||||
|
#else |
||||
|
# define LIFETIMEBOUND |
||||
|
#endif |
||||
|
|
||||
|
#endif // HUSH_ATTRIBUTES_H
|
@ -1,37 +1,20 @@ |
|||||
// Copyright (c) 2016-2022 The Hush developers
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
/******************************************************************************
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
* Copyright © 2014-2019 The SuperNET Developers. * |
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
* * |
// THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY
|
||||
* See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * |
// Instead, run: ./contrib/seeds/generate-seeds.py contrib/seeds
|
||||
* the top-level directory of this distribution for the individual copyright * |
|
||||
* holder information and the developer policies on copyright and licensing. * |
|
||||
* * |
|
||||
* Unless otherwise agreed in a custom licensing agreement, no part of the * |
|
||||
* SuperNET software, including this file may be copied, modified, propagated * |
|
||||
* or distributed except according to the terms contained in the LICENSE file * |
|
||||
* * |
|
||||
* Removal or modification of this copyright notice is prohibited. * |
|
||||
* * |
|
||||
******************************************************************************/ |
|
||||
|
|
||||
#ifndef HUSH_CHAINPARAMSSEEDS_H |
#ifndef HUSH_CHAINPARAMSSEEDS_H |
||||
#define HUSH_CHAINPARAMSSEEDS_H |
#define HUSH_CHAINPARAMSSEEDS_H |
||||
/**
|
// List of fixed seed nodes for the Hush network
|
||||
* List of fixed seed nodes for the bitcoin network |
// Each line contains a BIP155 serialized address.
|
||||
* AUTOGENERATED by contrib/seeds/generate-seeds.py |
//
|
||||
* |
static const uint8_t chainparams_seed_main[] = { |
||||
* Each line contains a 16-byte IPv6 address and a port. |
0x01,0x04,0xb9,0xf1,0x3d,0x2b,0x00,0x00, // 185.241.61.43
|
||||
* IPv4 as well as onion addresses are wrapped inside a IPv6 address accordingly. |
0x01,0x04,0x89,0x4a,0x04,0xc6,0x00,0x00, // 137.74.4.198
|
||||
*/ |
0x01,0x04,0x95,0x1c,0x66,0xdb,0x00,0x00, // 149.28.102.219
|
||||
static SeedSpec6 pnSeed6_main[] = { |
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x19,0x30,0xec}, 27485}, |
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x19,0x30,0xec}, 27487}, |
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x40,0x69,0x6f}, 27485}, |
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x40,0x69,0x6f}, 27487}, |
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x19,0x30,0x48}, 27485}, |
|
||||
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x19,0x30,0x48}, 27487} |
|
||||
}; |
}; |
||||
|
|
||||
static SeedSpec6 pnSeed6_test[] = { |
static const uint8_t chainparams_seed_test[] = { |
||||
|
0x01,0x04,0x01,0x02,0x03,0x04,0x00,0x00, // 1.2.3.4
|
||||
}; |
}; |
||||
#endif // HUSH_CHAINPARAMSSEEDS_H
|
#endif // HUSH_CHAINPARAMSSEEDS_H
|
||||
|
@ -0,0 +1,162 @@ |
|||||
|
// Copyright (c) 2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
// Based on https://github.com/mjosaarinen/tiny_sha3/blob/master/sha3.c
|
||||
|
// by Markku-Juhani O. Saarinen <mjos@iki.fi>
|
||||
|
|
||||
|
#include "crypto/sha3.h" |
||||
|
#include "crypto/common.h" |
||||
|
#include "span.h" |
||||
|
|
||||
|
#include <algorithm> |
||||
|
#include <array> // For std::begin and std::end. |
||||
|
|
||||
|
#include <stdint.h> |
||||
|
|
||||
|
// Internal implementation code.
|
||||
|
namespace |
||||
|
{ |
||||
|
uint64_t Rotl(uint64_t x, int n) { return (x << n) | (x >> (64 - n)); } |
||||
|
} // namespace
|
||||
|
|
||||
|
void KeccakF(uint64_t (&st)[25]) |
||||
|
{ |
||||
|
static constexpr uint64_t RNDC[24] = { |
||||
|
0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, |
||||
|
0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, |
||||
|
0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, |
||||
|
0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, |
||||
|
0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, |
||||
|
0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 |
||||
|
}; |
||||
|
static constexpr int ROUNDS = 24; |
||||
|
|
||||
|
for (int round = 0; round < ROUNDS; ++round) { |
||||
|
uint64_t bc0, bc1, bc2, bc3, bc4, t; |
||||
|
|
||||
|
// Theta
|
||||
|
bc0 = st[0] ^ st[5] ^ st[10] ^ st[15] ^ st[20]; |
||||
|
bc1 = st[1] ^ st[6] ^ st[11] ^ st[16] ^ st[21]; |
||||
|
bc2 = st[2] ^ st[7] ^ st[12] ^ st[17] ^ st[22]; |
||||
|
bc3 = st[3] ^ st[8] ^ st[13] ^ st[18] ^ st[23]; |
||||
|
bc4 = st[4] ^ st[9] ^ st[14] ^ st[19] ^ st[24]; |
||||
|
t = bc4 ^ Rotl(bc1, 1); st[0] ^= t; st[5] ^= t; st[10] ^= t; st[15] ^= t; st[20] ^= t; |
||||
|
t = bc0 ^ Rotl(bc2, 1); st[1] ^= t; st[6] ^= t; st[11] ^= t; st[16] ^= t; st[21] ^= t; |
||||
|
t = bc1 ^ Rotl(bc3, 1); st[2] ^= t; st[7] ^= t; st[12] ^= t; st[17] ^= t; st[22] ^= t; |
||||
|
t = bc2 ^ Rotl(bc4, 1); st[3] ^= t; st[8] ^= t; st[13] ^= t; st[18] ^= t; st[23] ^= t; |
||||
|
t = bc3 ^ Rotl(bc0, 1); st[4] ^= t; st[9] ^= t; st[14] ^= t; st[19] ^= t; st[24] ^= t; |
||||
|
|
||||
|
// Rho Pi
|
||||
|
t = st[1]; |
||||
|
bc0 = st[10]; st[10] = Rotl(t, 1); t = bc0; |
||||
|
bc0 = st[7]; st[7] = Rotl(t, 3); t = bc0; |
||||
|
bc0 = st[11]; st[11] = Rotl(t, 6); t = bc0; |
||||
|
bc0 = st[17]; st[17] = Rotl(t, 10); t = bc0; |
||||
|
bc0 = st[18]; st[18] = Rotl(t, 15); t = bc0; |
||||
|
bc0 = st[3]; st[3] = Rotl(t, 21); t = bc0; |
||||
|
bc0 = st[5]; st[5] = Rotl(t, 28); t = bc0; |
||||
|
bc0 = st[16]; st[16] = Rotl(t, 36); t = bc0; |
||||
|
bc0 = st[8]; st[8] = Rotl(t, 45); t = bc0; |
||||
|
bc0 = st[21]; st[21] = Rotl(t, 55); t = bc0; |
||||
|
bc0 = st[24]; st[24] = Rotl(t, 2); t = bc0; |
||||
|
bc0 = st[4]; st[4] = Rotl(t, 14); t = bc0; |
||||
|
bc0 = st[15]; st[15] = Rotl(t, 27); t = bc0; |
||||
|
bc0 = st[23]; st[23] = Rotl(t, 41); t = bc0; |
||||
|
bc0 = st[19]; st[19] = Rotl(t, 56); t = bc0; |
||||
|
bc0 = st[13]; st[13] = Rotl(t, 8); t = bc0; |
||||
|
bc0 = st[12]; st[12] = Rotl(t, 25); t = bc0; |
||||
|
bc0 = st[2]; st[2] = Rotl(t, 43); t = bc0; |
||||
|
bc0 = st[20]; st[20] = Rotl(t, 62); t = bc0; |
||||
|
bc0 = st[14]; st[14] = Rotl(t, 18); t = bc0; |
||||
|
bc0 = st[22]; st[22] = Rotl(t, 39); t = bc0; |
||||
|
bc0 = st[9]; st[9] = Rotl(t, 61); t = bc0; |
||||
|
bc0 = st[6]; st[6] = Rotl(t, 20); t = bc0; |
||||
|
st[1] = Rotl(t, 44); |
||||
|
|
||||
|
// Chi Iota
|
||||
|
bc0 = st[0]; bc1 = st[1]; bc2 = st[2]; bc3 = st[3]; bc4 = st[4]; |
||||
|
st[0] = bc0 ^ (~bc1 & bc2) ^ RNDC[round]; |
||||
|
st[1] = bc1 ^ (~bc2 & bc3); |
||||
|
st[2] = bc2 ^ (~bc3 & bc4); |
||||
|
st[3] = bc3 ^ (~bc4 & bc0); |
||||
|
st[4] = bc4 ^ (~bc0 & bc1); |
||||
|
bc0 = st[5]; bc1 = st[6]; bc2 = st[7]; bc3 = st[8]; bc4 = st[9]; |
||||
|
st[5] = bc0 ^ (~bc1 & bc2); |
||||
|
st[6] = bc1 ^ (~bc2 & bc3); |
||||
|
st[7] = bc2 ^ (~bc3 & bc4); |
||||
|
st[8] = bc3 ^ (~bc4 & bc0); |
||||
|
st[9] = bc4 ^ (~bc0 & bc1); |
||||
|
bc0 = st[10]; bc1 = st[11]; bc2 = st[12]; bc3 = st[13]; bc4 = st[14]; |
||||
|
st[10] = bc0 ^ (~bc1 & bc2); |
||||
|
st[11] = bc1 ^ (~bc2 & bc3); |
||||
|
st[12] = bc2 ^ (~bc3 & bc4); |
||||
|
st[13] = bc3 ^ (~bc4 & bc0); |
||||
|
st[14] = bc4 ^ (~bc0 & bc1); |
||||
|
bc0 = st[15]; bc1 = st[16]; bc2 = st[17]; bc3 = st[18]; bc4 = st[19]; |
||||
|
st[15] = bc0 ^ (~bc1 & bc2); |
||||
|
st[16] = bc1 ^ (~bc2 & bc3); |
||||
|
st[17] = bc2 ^ (~bc3 & bc4); |
||||
|
st[18] = bc3 ^ (~bc4 & bc0); |
||||
|
st[19] = bc4 ^ (~bc0 & bc1); |
||||
|
bc0 = st[20]; bc1 = st[21]; bc2 = st[22]; bc3 = st[23]; bc4 = st[24]; |
||||
|
st[20] = bc0 ^ (~bc1 & bc2); |
||||
|
st[21] = bc1 ^ (~bc2 & bc3); |
||||
|
st[22] = bc2 ^ (~bc3 & bc4); |
||||
|
st[23] = bc3 ^ (~bc4 & bc0); |
||||
|
st[24] = bc4 ^ (~bc0 & bc1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
SHA3_256_& SHA3_256_::Write(Span<const unsigned char> data) |
||||
|
{ |
||||
|
if (m_bufsize && m_bufsize + data.size() >= sizeof(m_buffer)) { |
||||
|
// Fill the buffer and process it.
|
||||
|
std::copy(data.begin(), data.begin() + sizeof(m_buffer) - m_bufsize, m_buffer + m_bufsize); |
||||
|
data = data.subspan(sizeof(m_buffer) - m_bufsize); |
||||
|
m_state[m_pos++] ^= ReadLE64(m_buffer); |
||||
|
m_bufsize = 0; |
||||
|
if (m_pos == RATE_BUFFERS) { |
||||
|
KeccakF(m_state); |
||||
|
m_pos = 0; |
||||
|
} |
||||
|
} |
||||
|
while (data.size() >= sizeof(m_buffer)) { |
||||
|
// Process chunks directly from the buffer.
|
||||
|
m_state[m_pos++] ^= ReadLE64(data.data()); |
||||
|
data = data.subspan(8); |
||||
|
if (m_pos == RATE_BUFFERS) { |
||||
|
KeccakF(m_state); |
||||
|
m_pos = 0; |
||||
|
} |
||||
|
} |
||||
|
if (data.size()) { |
||||
|
// Keep the remainder in the buffer.
|
||||
|
std::copy(data.begin(), data.end(), m_buffer + m_bufsize); |
||||
|
m_bufsize += data.size(); |
||||
|
} |
||||
|
return *this; |
||||
|
} |
||||
|
|
||||
|
SHA3_256_& SHA3_256_::Finalize(Span<unsigned char> output) |
||||
|
{ |
||||
|
assert(output.size() == OUTPUT_SIZE); |
||||
|
std::fill(m_buffer + m_bufsize, m_buffer + sizeof(m_buffer), 0); |
||||
|
m_buffer[m_bufsize] ^= 0x06; |
||||
|
m_state[m_pos] ^= ReadLE64(m_buffer); |
||||
|
m_state[RATE_BUFFERS - 1] ^= 0x8000000000000000; |
||||
|
KeccakF(m_state); |
||||
|
for (unsigned i = 0; i < 4; ++i) { |
||||
|
WriteLE64(output.data() + 8 * i, m_state[i]); |
||||
|
} |
||||
|
return *this; |
||||
|
} |
||||
|
|
||||
|
SHA3_256_& SHA3_256_::Reset() |
||||
|
{ |
||||
|
m_bufsize = 0; |
||||
|
m_pos = 0; |
||||
|
std::fill(std::begin(m_state), std::end(m_state), 0); |
||||
|
return *this; |
||||
|
} |
@ -0,0 +1,42 @@ |
|||||
|
// Copyright (c) 2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef HUSH_CRYPTO_SHA3_H |
||||
|
#define HUSH_CRYPTO_SHA3_H |
||||
|
|
||||
|
#include "span.h" |
||||
|
|
||||
|
#include <stdint.h> |
||||
|
#include <stdlib.h> |
||||
|
|
||||
|
//! The Keccak-f[1600] transform.
|
||||
|
void KeccakF(uint64_t (&st)[25]); |
||||
|
|
||||
|
class SHA3_256_ |
||||
|
{ |
||||
|
private: |
||||
|
uint64_t m_state[25] = {0}; |
||||
|
unsigned char m_buffer[8]; |
||||
|
unsigned m_bufsize = 0; |
||||
|
unsigned m_pos = 0; |
||||
|
|
||||
|
//! Sponge rate in bits.
|
||||
|
static constexpr unsigned RATE_BITS = 1088; |
||||
|
|
||||
|
//! Sponge rate expressed as a multiple of the buffer size.
|
||||
|
static constexpr unsigned RATE_BUFFERS = RATE_BITS / (8 * sizeof(m_buffer)); |
||||
|
|
||||
|
static_assert(RATE_BITS % (8 * sizeof(m_buffer)) == 0, "Rate must be a multiple of 8 bytes"); |
||||
|
|
||||
|
public: |
||||
|
static constexpr size_t OUTPUT_SIZE = 32; |
||||
|
|
||||
|
SHA3_256_() {} |
||||
|
SHA3_256_& Write(Span<const unsigned char> data); |
||||
|
SHA3_256_& Finalize(Span<unsigned char> output); |
||||
|
SHA3_256_& Reset(); |
||||
|
}; |
||||
|
|
||||
|
#endif // HUSH_CRYPTO_SHA3_H
|
@ -0,0 +1,441 @@ |
|||||
|
// Copyright (c) 2020-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#include <init.h> |
||||
|
#include <chainparams.h> |
||||
|
#include <compat.h> |
||||
|
#include <compat/endian.h> |
||||
|
#include <crypto/sha256.h> |
||||
|
#include <fs.h> |
||||
|
#include <i2p.h> |
||||
|
#include <netbase.h> |
||||
|
#include <random.h> |
||||
|
#include <util/strencodings.h> |
||||
|
#include <tinyformat.h> |
||||
|
#include <util/readwritefile.h> |
||||
|
#include <util/sock.h> |
||||
|
#include <util/spanparsing.h> |
||||
|
#include <util.h> |
||||
|
|
||||
|
#include <chrono> |
||||
|
#include <memory> |
||||
|
#include <stdexcept> |
||||
|
#include <string> |
||||
|
|
||||
|
namespace i2p { |
||||
|
|
||||
|
/**
|
||||
|
* Swap Standard Base64 <-> I2P Base64. |
||||
|
* Standard Base64 uses `+` and `/` as last two characters of its alphabet. |
||||
|
* I2P Base64 uses `-` and `~` respectively. |
||||
|
* So it is easy to detect in which one is the input and convert to the other. |
||||
|
* @param[in] from Input to convert. |
||||
|
* @return converted `from` |
||||
|
*/ |
||||
|
static std::string SwapBase64(const std::string& from) |
||||
|
{ |
||||
|
std::string to; |
||||
|
to.resize(from.size()); |
||||
|
for (size_t i = 0; i < from.size(); ++i) { |
||||
|
switch (from[i]) { |
||||
|
case '-': |
||||
|
to[i] = '+'; |
||||
|
break; |
||||
|
case '~': |
||||
|
to[i] = '/'; |
||||
|
break; |
||||
|
case '+': |
||||
|
to[i] = '-'; |
||||
|
break; |
||||
|
case '/': |
||||
|
to[i] = '~'; |
||||
|
break; |
||||
|
default: |
||||
|
to[i] = from[i]; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
return to; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Decode an I2P-style Base64 string. |
||||
|
* @param[in] i2p_b64 I2P-style Base64 string. |
||||
|
* @return decoded `i2p_b64` |
||||
|
* @throw std::runtime_error if decoding fails |
||||
|
*/ |
||||
|
static Binary DecodeI2PBase64(const std::string& i2p_b64) |
||||
|
{ |
||||
|
const std::string& std_b64 = SwapBase64(i2p_b64); |
||||
|
bool invalid; |
||||
|
Binary decoded = DecodeBase64(std_b64.c_str(), &invalid); |
||||
|
if (invalid) { |
||||
|
throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64)); |
||||
|
} |
||||
|
return decoded; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Derive the .b32.i2p address of an I2P destination (binary). |
||||
|
* @param[in] dest I2P destination. |
||||
|
* @return the address that corresponds to `dest` |
||||
|
* @throw std::runtime_error if conversion fails |
||||
|
*/ |
||||
|
static CNetAddr DestBinToAddr(const Binary& dest) |
||||
|
{ |
||||
|
CSHA256 hasher; |
||||
|
hasher.Write(dest.data(), dest.size()); |
||||
|
unsigned char hash[CSHA256::OUTPUT_SIZE]; |
||||
|
hasher.Finalize(hash); |
||||
|
|
||||
|
CNetAddr addr; |
||||
|
const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p"; |
||||
|
if (!addr.SetSpecial(addr_str)) { |
||||
|
throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str)); |
||||
|
} |
||||
|
|
||||
|
return addr; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Derive the .b32.i2p address of an I2P destination (I2P-style Base64). |
||||
|
* @param[in] dest I2P destination. |
||||
|
* @return the address that corresponds to `dest` |
||||
|
* @throw std::runtime_error if conversion fails |
||||
|
*/ |
||||
|
static CNetAddr DestB64ToAddr(const std::string& dest) |
||||
|
{ |
||||
|
const Binary& decoded = DecodeI2PBase64(dest); |
||||
|
return DestBinToAddr(decoded); |
||||
|
} |
||||
|
|
||||
|
namespace sam { |
||||
|
|
||||
|
Session::Session(const fs::path& private_key_file, |
||||
|
const CService& control_host) |
||||
|
: m_private_key_file(private_key_file), m_control_host(control_host), |
||||
|
m_control_sock(std::unique_ptr<Sock>(new Sock(INVALID_SOCKET))) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
Session::~Session() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
bool Session::Check() |
||||
|
{ |
||||
|
try { |
||||
|
LOCK(cs_i2p); |
||||
|
CreateIfNotCreatedAlready(); |
||||
|
return true; |
||||
|
} catch (const std::runtime_error& e) { |
||||
|
LogPrint("i2p","I2P: Error Checking Session: %s\n", e.what()); |
||||
|
CheckControlSock(); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool Session::Listen(Connection& conn) |
||||
|
{ |
||||
|
try { |
||||
|
LOCK(cs_i2p); |
||||
|
CreateIfNotCreatedAlready(); |
||||
|
conn.me = m_my_addr; |
||||
|
conn.sock = StreamAccept(); |
||||
|
return true; |
||||
|
} catch (const std::runtime_error& e) { |
||||
|
LogPrint("i2p","I2P: Error listening: %s\n", e.what()); |
||||
|
CheckControlSock(); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool Session::Accept(Connection& conn) |
||||
|
{ |
||||
|
try { |
||||
|
while (true) { |
||||
|
|
||||
|
// boost::this_thread::interruption_point();
|
||||
|
if (ShutdownRequested()) { |
||||
|
Disconnect(); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
Sock::Event occurred; |
||||
|
if (!conn.sock->Wait(std::chrono::milliseconds{MAX_WAIT_FOR_IO}, Sock::RECV, &occurred)) { |
||||
|
throw std::runtime_error("wait on socket failed"); |
||||
|
} |
||||
|
|
||||
|
if ((occurred & Sock::RECV) == 0) { |
||||
|
// Timeout, no incoming connections within MAX_WAIT_FOR_IO.
|
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
const std::string& peer_dest = |
||||
|
conn.sock->RecvUntilTerminator('\n', std::chrono::milliseconds{MAX_WAIT_FOR_IO}, MAX_MSG_SIZE); |
||||
|
|
||||
|
if (ShutdownRequested()) { |
||||
|
Disconnect(); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort()); |
||||
|
return true; |
||||
|
} |
||||
|
} catch (const std::runtime_error& e) { |
||||
|
LogPrint("i2p","I2P: Error accepting: %s\n", e.what()); |
||||
|
CheckControlSock(); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) |
||||
|
{ |
||||
|
proxy_error = true; |
||||
|
|
||||
|
std::string session_id; |
||||
|
std::unique_ptr<Sock> sock; |
||||
|
conn.peer = to; |
||||
|
|
||||
|
try { |
||||
|
{ |
||||
|
LOCK(cs_i2p); |
||||
|
CreateIfNotCreatedAlready(); |
||||
|
session_id = m_session_id; |
||||
|
conn.me = m_my_addr; |
||||
|
sock = Hello(); |
||||
|
} |
||||
|
|
||||
|
const Reply& lookup_reply = |
||||
|
SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP())); |
||||
|
|
||||
|
const std::string& dest = lookup_reply.Get("VALUE"); |
||||
|
|
||||
|
const Reply& connect_reply = SendRequestAndGetReply( |
||||
|
*sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest), |
||||
|
false); |
||||
|
|
||||
|
const std::string& result = connect_reply.Get("RESULT"); |
||||
|
|
||||
|
if (result == "OK") { |
||||
|
conn.sock = std::move(sock); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (result == "INVALID_ID") { |
||||
|
LOCK(cs_i2p); |
||||
|
Disconnect(); |
||||
|
throw std::runtime_error("Invalid session id"); |
||||
|
} |
||||
|
|
||||
|
if (result == "CANT_REACH_PEER" || result == "TIMEOUT" || "KEY_NOT_FOUND") { |
||||
|
proxy_error = false; |
||||
|
} |
||||
|
|
||||
|
throw std::runtime_error(strprintf("\"%s\"", connect_reply.full)); |
||||
|
} catch (const std::runtime_error& e) { |
||||
|
LogPrint("i2p","I2P: Error connecting to %s: %s\n", to.ToString(), e.what()); |
||||
|
CheckControlSock(); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Private methods
|
||||
|
|
||||
|
std::string Session::Reply::Get(const std::string& key) const |
||||
|
{ |
||||
|
const auto& pos = keys.find(key); |
||||
|
if (pos == keys.end() || !pos->second.has_value()) { |
||||
|
throw std::runtime_error( |
||||
|
strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full)); |
||||
|
} |
||||
|
return pos->second.value(); |
||||
|
} |
||||
|
|
||||
|
Session::Reply Session::SendRequestAndGetReply(const Sock& sock, |
||||
|
const std::string& request, |
||||
|
bool check_result_ok) const |
||||
|
{ |
||||
|
sock.SendComplete(request + "\n", std::chrono::milliseconds{MAX_WAIT_FOR_IO}); |
||||
|
|
||||
|
Reply reply; |
||||
|
|
||||
|
// Don't log the full "SESSION CREATE ..." because it contains our private key.
|
||||
|
reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request; |
||||
|
|
||||
|
// It could take a few minutes for the I2P router to reply as it is querying the I2P network
|
||||
|
// (when doing name lookup, for example).
|
||||
|
|
||||
|
reply.full = sock.RecvUntilTerminator('\n', std::chrono::minutes{3}, MAX_MSG_SIZE); |
||||
|
|
||||
|
for (const auto& kv : spanparsing::Split(reply.full, ' ')) { |
||||
|
const auto& pos = std::find(kv.begin(), kv.end(), '='); |
||||
|
if (pos != kv.end()) { |
||||
|
reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()}); |
||||
|
} else { |
||||
|
reply.keys.emplace(std::string{kv.begin(), kv.end()}, boost::none); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
LogPrint("i2p","I2P: Handshake reply %s\n", reply.full); |
||||
|
|
||||
|
if (check_result_ok && reply.Get("RESULT") != "OK") { |
||||
|
if (!ShutdownRequested()) { |
||||
|
throw std::runtime_error(strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return reply; |
||||
|
} |
||||
|
|
||||
|
std::unique_ptr<Sock> Session::Hello() const |
||||
|
{ |
||||
|
auto sock = CreateSock(m_control_host); |
||||
|
|
||||
|
if (!sock) { |
||||
|
throw std::runtime_error("Cannot create socket"); |
||||
|
} |
||||
|
|
||||
|
if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout)) { |
||||
|
throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString())); |
||||
|
} |
||||
|
|
||||
|
SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1"); |
||||
|
|
||||
|
return sock; |
||||
|
} |
||||
|
|
||||
|
void Session::CheckControlSock() |
||||
|
{ |
||||
|
LOCK(cs_i2p); |
||||
|
|
||||
|
std::string errmsg; |
||||
|
if (!m_control_sock->IsConnected(errmsg)) { |
||||
|
LogPrint("i2p","I2P: Control socket error: %s\n", errmsg); |
||||
|
Disconnect(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void Session::DestGenerate(const Sock& sock) |
||||
|
{ |
||||
|
// https://geti2p.net/spec/common-structures#key-certificates
|
||||
|
// "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations".
|
||||
|
// Use "7" because i2pd <2.24.0 does not recognize the textual form.
|
||||
|
const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false); |
||||
|
|
||||
|
m_private_key = DecodeI2PBase64(reply.Get("PRIV")); |
||||
|
} |
||||
|
|
||||
|
void Session::GenerateAndSavePrivateKey(const Sock& sock) |
||||
|
{ |
||||
|
DestGenerate(sock); |
||||
|
|
||||
|
// umask is set to 077 in init.cpp, which is ok (unless -sysperms is given)
|
||||
|
if (!WriteBinaryFile(m_private_key_file, |
||||
|
std::string(m_private_key.begin(), m_private_key.end()))) { |
||||
|
throw std::runtime_error( |
||||
|
strprintf("Cannot save I2P private key to %s", m_private_key_file)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Binary Session::MyDestination() const |
||||
|
{ |
||||
|
// From https://geti2p.net/spec/common-structures#destination:
|
||||
|
// "They are 387 bytes plus the certificate length specified at bytes 385-386, which may be
|
||||
|
// non-zero"
|
||||
|
static constexpr size_t DEST_LEN_BASE = 387; |
||||
|
static constexpr size_t CERT_LEN_POS = 385; |
||||
|
|
||||
|
uint16_t cert_len; |
||||
|
memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len)); |
||||
|
cert_len = be16toh(cert_len); |
||||
|
|
||||
|
const size_t dest_len = DEST_LEN_BASE + cert_len; |
||||
|
|
||||
|
return Binary{m_private_key.begin(), m_private_key.begin() + dest_len}; |
||||
|
} |
||||
|
|
||||
|
void Session::CreateIfNotCreatedAlready() |
||||
|
{ |
||||
|
LOCK(cs_i2p); |
||||
|
|
||||
|
std::string errmsg; |
||||
|
if (m_control_sock->IsConnected(errmsg)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LogPrint("i2p","I2P: Creating SAM session with %s\n", m_control_host.ToString()); |
||||
|
|
||||
|
auto sock = Hello(); |
||||
|
|
||||
|
const std::pair<bool,std::string> i2pRead = ReadBinaryFile(m_private_key_file); |
||||
|
if (i2pRead.first) { |
||||
|
m_private_key.assign(i2pRead.second.begin(), i2pRead.second.end()); |
||||
|
} else { |
||||
|
GenerateAndSavePrivateKey(*sock); |
||||
|
} |
||||
|
|
||||
|
const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs
|
||||
|
const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key)); |
||||
|
|
||||
|
SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", |
||||
|
session_id, private_key_b64)); |
||||
|
|
||||
|
m_my_addr = CService(DestBinToAddr(MyDestination()), Params().GetDefaultPort()); |
||||
|
m_session_id = session_id; |
||||
|
m_control_sock = std::move(sock); |
||||
|
|
||||
|
LogPrint("i2p","I2P: SAM session created: session id=%s, my address=%s\n", m_session_id, |
||||
|
m_my_addr.ToString()); |
||||
|
} |
||||
|
|
||||
|
std::unique_ptr<Sock> Session::StreamAccept() |
||||
|
{ |
||||
|
auto sock = Hello(); |
||||
|
|
||||
|
const Reply& reply = SendRequestAndGetReply( |
||||
|
*sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false); |
||||
|
|
||||
|
const std::string& result = reply.Get("RESULT"); |
||||
|
|
||||
|
if (result == "OK") { |
||||
|
return sock; |
||||
|
} |
||||
|
|
||||
|
if (result == "INVALID_ID") { |
||||
|
// If our session id is invalid, then force session re-creation on next usage.
|
||||
|
Disconnect(); |
||||
|
} |
||||
|
|
||||
|
throw std::runtime_error(strprintf("\"%s\"", reply.full)); |
||||
|
} |
||||
|
|
||||
|
void Session::Disconnect() |
||||
|
{ |
||||
|
LOCK(cs_i2p); |
||||
|
try |
||||
|
{ |
||||
|
if (m_control_sock->Get() != INVALID_SOCKET) { |
||||
|
if (m_session_id.empty()) { |
||||
|
LogPrint("i2p","I2P: Destroying incomplete session\n"); |
||||
|
} else { |
||||
|
LogPrint("i2p","I2P: Destroying session %s\n", m_session_id); |
||||
|
} |
||||
|
} |
||||
|
m_control_sock->Reset(); |
||||
|
m_session_id.clear(); |
||||
|
} |
||||
|
catch(std::bad_alloc&) |
||||
|
{ |
||||
|
// when the node is shutting down, the call above might use invalid memory resulting in a
|
||||
|
// std::bad_alloc exception when instantiating internal objs for handling log category
|
||||
|
LogPrintf("(node is probably shutting down) Destroying session=%d\n", m_session_id); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} // namespace sam
|
||||
|
} // namespace i2p
|
@ -0,0 +1,256 @@ |
|||||
|
// Copyright (c) 2020-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef HUSH_I2P_H |
||||
|
#define HUSH_I2P_H |
||||
|
|
||||
|
#include <compat.h> |
||||
|
#include <fs.h> |
||||
|
#include <netaddress.h> |
||||
|
#include <sync.h> |
||||
|
#include <util/sock.h> |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <optional> |
||||
|
#include <string> |
||||
|
#include <unordered_map> |
||||
|
#include <vector> |
||||
|
|
||||
|
namespace i2p { |
||||
|
|
||||
|
/**
|
||||
|
* Binary data. |
||||
|
*/ |
||||
|
using Binary = std::vector<uint8_t>; |
||||
|
|
||||
|
/**
|
||||
|
* An established connection with another peer. |
||||
|
*/ |
||||
|
struct Connection { |
||||
|
/** Connected socket. */ |
||||
|
std::unique_ptr<Sock> sock; |
||||
|
|
||||
|
/** Our I2P address. */ |
||||
|
CService me; |
||||
|
|
||||
|
/** The peer's I2P address. */ |
||||
|
CService peer; |
||||
|
}; |
||||
|
|
||||
|
namespace sam { |
||||
|
|
||||
|
/**
|
||||
|
* The maximum size of an incoming message from the I2P SAM proxy (in bytes). |
||||
|
* Used to avoid a runaway proxy from sending us an "unlimited" amount of data without a terminator. |
||||
|
* The longest known message is ~1400 bytes, so this is high enough not to be triggered during |
||||
|
* normal operation, yet low enough to avoid a malicious proxy from filling our memory. |
||||
|
*/ |
||||
|
static constexpr size_t MAX_MSG_SIZE{65536}; |
||||
|
|
||||
|
/**
|
||||
|
* I2P SAM session. |
||||
|
*/ |
||||
|
class Session |
||||
|
{ |
||||
|
public: |
||||
|
/**
|
||||
|
* Construct a session. This will not initiate any IO, the session will be lazily created |
||||
|
* later when first used. |
||||
|
* @param[in] private_key_file Path to a private key file. If the file does not exist then the |
||||
|
* private key will be generated and saved into the file. |
||||
|
* @param[in] control_host Location of the SAM proxy. |
||||
|
*/ |
||||
|
Session(const fs::path& private_key_file, |
||||
|
const CService& control_host); |
||||
|
|
||||
|
/**
|
||||
|
* Destroy the session, closing the internally used sockets. The sockets that have been |
||||
|
* returned by `Accept()` or `Connect()` will not be closed, but they will be closed by |
||||
|
* the SAM proxy because the session is destroyed. So they will return an error next time |
||||
|
* we try to read or write to them. |
||||
|
*/ |
||||
|
~Session(); |
||||
|
|
||||
|
/**
|
||||
|
* Check the control sock and restart if needed |
||||
|
*/ |
||||
|
bool Check(); |
||||
|
|
||||
|
/**
|
||||
|
* Start listening for an incoming connection. |
||||
|
* @param[out] conn Upon successful completion the `sock` and `me` members will be set |
||||
|
* to the listening socket and address. |
||||
|
* @return true on success |
||||
|
*/ |
||||
|
bool Listen(Connection& conn); |
||||
|
|
||||
|
/**
|
||||
|
* Wait for and accept a new incoming connection. |
||||
|
* @param[in,out] conn The `sock` member is used for waiting and accepting. Upon successful |
||||
|
* completion the `peer` member will be set to the address of the incoming peer. |
||||
|
* @return true on success |
||||
|
*/ |
||||
|
bool Accept(Connection& conn); |
||||
|
|
||||
|
/**
|
||||
|
* Connect to an I2P peer. |
||||
|
* @param[in] to Peer to connect to. |
||||
|
* @param[out] conn Established connection. Only set if `true` is returned. |
||||
|
* @param[out] proxy_error If an error occurs due to proxy or general network failure, then |
||||
|
* this is set to `true`. If an error occurs due to unreachable peer (likely peer is down), then |
||||
|
* it is set to `false`. Only set if `false` is returned. |
||||
|
* @return true on success |
||||
|
*/ |
||||
|
bool Connect(const CService& to, Connection& conn, bool& proxy_error); |
||||
|
|
||||
|
protected: |
||||
|
|
||||
|
CCriticalSection cs_i2p; |
||||
|
|
||||
|
private: |
||||
|
/**
|
||||
|
* A reply from the SAM proxy. |
||||
|
*/ |
||||
|
struct Reply { |
||||
|
/**
|
||||
|
* Full, unparsed reply. |
||||
|
*/ |
||||
|
std::string full; |
||||
|
|
||||
|
/**
|
||||
|
* Request, used for detailed error reporting. |
||||
|
*/ |
||||
|
std::string request; |
||||
|
|
||||
|
/**
|
||||
|
* A map of keywords from the parsed reply. |
||||
|
* For example, if the reply is "A=X B C=YZ", then the map will be |
||||
|
* keys["A"] == "X" |
||||
|
* keys["B"] == (empty std::optional) |
||||
|
* keys["C"] == "YZ" |
||||
|
*/ |
||||
|
std::unordered_map<std::string, boost::optional<std::string>> keys; |
||||
|
|
||||
|
/**
|
||||
|
* Get the value of a given key. |
||||
|
* For example if the reply is "A=X B" then: |
||||
|
* Value("A") -> "X" |
||||
|
* Value("B") -> throws |
||||
|
* Value("C") -> throws |
||||
|
* @param[in] key Key whose value to retrieve |
||||
|
* @returns the key's value |
||||
|
* @throws std::runtime_error if the key is not present or if it has no value |
||||
|
*/ |
||||
|
std::string Get(const std::string& key) const; |
||||
|
}; |
||||
|
|
||||
|
/**
|
||||
|
* Send request and get a reply from the SAM proxy. |
||||
|
* @param[in] sock A socket that is connected to the SAM proxy. |
||||
|
* @param[in] request Raw request to send, a newline terminator is appended to it. |
||||
|
* @param[in] check_result_ok If true then after receiving the reply a check is made |
||||
|
* whether it contains "RESULT=OK" and an exception is thrown if it does not. |
||||
|
* @throws std::runtime_error if an error occurs |
||||
|
*/ |
||||
|
Reply SendRequestAndGetReply(const Sock& sock, |
||||
|
const std::string& request, |
||||
|
bool check_result_ok = true) const; |
||||
|
|
||||
|
/**
|
||||
|
* Open a new connection to the SAM proxy. |
||||
|
* @return a connected socket |
||||
|
* @throws std::runtime_error if an error occurs |
||||
|
*/ |
||||
|
std::unique_ptr<Sock> Hello() const EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* Check the control socket for errors and possibly disconnect. |
||||
|
*/ |
||||
|
void CheckControlSock(); |
||||
|
|
||||
|
/**
|
||||
|
* Generate a new destination with the SAM proxy and set `m_private_key` to it. |
||||
|
* @param[in] sock Socket to use for talking to the SAM proxy. |
||||
|
* @throws std::runtime_error if an error occurs |
||||
|
*/ |
||||
|
void DestGenerate(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* Generate a new destination with the SAM proxy, set `m_private_key` to it and save |
||||
|
* it on disk to `m_private_key_file`. |
||||
|
* @param[in] sock Socket to use for talking to the SAM proxy. |
||||
|
* @throws std::runtime_error if an error occurs |
||||
|
*/ |
||||
|
void GenerateAndSavePrivateKey(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* Derive own destination from `m_private_key`. |
||||
|
* @see https://geti2p.net/spec/common-structures#destination
|
||||
|
* @return an I2P destination |
||||
|
*/ |
||||
|
Binary MyDestination() const EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* Create the session if not already created. Reads the private key file and connects to the |
||||
|
* SAM proxy. |
||||
|
* @throws std::runtime_error if an error occurs |
||||
|
*/ |
||||
|
void CreateIfNotCreatedAlready() EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* Open a new connection to the SAM proxy and issue "STREAM ACCEPT" request using the existing |
||||
|
* session id. |
||||
|
* @return the idle socket that is waiting for a peer to connect to us |
||||
|
* @throws std::runtime_error if an error occurs |
||||
|
*/ |
||||
|
std::unique_ptr<Sock> StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* Destroy the session, closing the internally used sockets. |
||||
|
*/ |
||||
|
void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* The name of the file where this peer's private key is stored (in binary). |
||||
|
*/ |
||||
|
const fs::path m_private_key_file; |
||||
|
|
||||
|
/**
|
||||
|
* The host and port of the SAM control service. |
||||
|
*/ |
||||
|
const CService m_control_host; |
||||
|
|
||||
|
/**
|
||||
|
* The private key of this peer. |
||||
|
* @see The reply to the "DEST GENERATE" command in https://geti2p.net/en/docs/api/samv3
|
||||
|
*/ |
||||
|
Binary m_private_key GUARDED_BY(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* SAM control socket. |
||||
|
* Used to connect to the I2P SAM service and create a session |
||||
|
* ("SESSION CREATE"). With the established session id we later open |
||||
|
* other connections to the SAM service to accept incoming I2P |
||||
|
* connections and make outgoing ones. |
||||
|
* See https://geti2p.net/en/docs/api/samv3
|
||||
|
*/ |
||||
|
std::unique_ptr<Sock> m_control_sock GUARDED_BY(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* Our .b32.i2p address. |
||||
|
* Derived from `m_private_key`. |
||||
|
*/ |
||||
|
CService m_my_addr GUARDED_BY(cs_i2p); |
||||
|
|
||||
|
/**
|
||||
|
* SAM session id. |
||||
|
*/ |
||||
|
std::string m_session_id GUARDED_BY(cs_i2p); |
||||
|
}; |
||||
|
|
||||
|
} // namespace sam
|
||||
|
} // namespace i2p
|
||||
|
|
||||
|
#endif /* HUSH_I2P_H */ |
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,534 @@ |
|||||
|
// Copyright (c) 2009-2015 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef HUSH_NETADDRESS_H |
||||
|
#define HUSH_NETADDRESS_H |
||||
|
|
||||
|
#if defined(HAVE_CONFIG_H) |
||||
|
#include "config/bitcoin-config.h" |
||||
|
#endif |
||||
|
|
||||
|
#include "attributes.h" |
||||
|
#include "compat.h" |
||||
|
#include "prevector.h" |
||||
|
#include "serialize.h" |
||||
|
#include "tinyformat.h" |
||||
|
#include "util/strencodings.h" |
||||
|
#include "util/string.h" |
||||
|
|
||||
|
#include <array> |
||||
|
#include <cstdint> |
||||
|
#include <ios> |
||||
|
#include <string> |
||||
|
#include <vector> |
||||
|
|
||||
|
/**
|
||||
|
* A flag that is ORed into the protocol version to designate that addresses |
||||
|
* should be serialized in (unserialized from) v2 format (BIP155). |
||||
|
* Make sure that this does not collide with any of the values in `version.h` |
||||
|
* or with `SERIALIZE_TRANSACTION_NO_WITNESS`. |
||||
|
*/ |
||||
|
static const int ADDRV2_FORMAT = 0x20000000; |
||||
|
|
||||
|
/**
|
||||
|
* A network type. |
||||
|
* @note An address may belong to more than one network, for example `10.0.0.1` |
||||
|
* belongs to both `NET_UNROUTABLE` and `NET_IPV4`. |
||||
|
* Keep these sequential starting from 0 and `NET_MAX` as the last entry. |
||||
|
* We have loops like `for (int i = 0; i < NET_MAX; i++)` that expect to iterate |
||||
|
* over all enum values and also `GetExtNetwork()` "extends" this enum by |
||||
|
* introducing standalone constants starting from `NET_MAX`. |
||||
|
*/ |
||||
|
enum Network |
||||
|
{ |
||||
|
/// Addresses from these networks are not publicly routable on the global Internet.
|
||||
|
NET_UNROUTABLE = 0, |
||||
|
|
||||
|
/// IPv4
|
||||
|
NET_IPV4, |
||||
|
|
||||
|
/// IPv6
|
||||
|
NET_IPV6, |
||||
|
|
||||
|
/// TOR (v2 or v3)
|
||||
|
NET_ONION, |
||||
|
|
||||
|
/// I2P
|
||||
|
NET_I2P, |
||||
|
|
||||
|
/// CJDNS
|
||||
|
NET_CJDNS, |
||||
|
|
||||
|
/// A set of addresses that represent the hash of a string or FQDN. We use
|
||||
|
/// them in CAddrMan to keep track of which DNS seeds were used.
|
||||
|
NET_INTERNAL, |
||||
|
|
||||
|
/// Dummy value to indicate the number of NET_* constants.
|
||||
|
NET_MAX, |
||||
|
}; |
||||
|
|
||||
|
/// Prefix of an IPv6 address when it contains an embedded IPv4 address.
|
||||
|
/// Used when (un)serializing addresses in ADDRv1 format (pre-BIP155).
|
||||
|
static const std::array<uint8_t, 12> IPV4_IN_IPV6_PREFIX{ |
||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF |
||||
|
}; |
||||
|
|
||||
|
/// Prefix of an IPv6 address when it contains an embedded TORv2 address.
|
||||
|
/// Used when (un)serializing addresses in ADDRv1 format (pre-BIP155).
|
||||
|
/// Such dummy IPv6 addresses are guaranteed to not be publicly routable as they
|
||||
|
/// fall under RFC4193's fc00::/7 subnet allocated to unique-local addresses.
|
||||
|
static const std::array<uint8_t, 6> TORV2_IN_IPV6_PREFIX{ |
||||
|
0xFD, 0x87, 0xD8, 0x7E, 0xEB, 0x43 |
||||
|
}; |
||||
|
|
||||
|
/// Prefix of an IPv6 address when it contains an embedded "internal" address.
|
||||
|
/// Used when (un)serializing addresses in ADDRv1 format (pre-BIP155).
|
||||
|
/// The prefix comes from 0xFD + SHA256("bitcoin")[0:5].
|
||||
|
/// Such dummy IPv6 addresses are guaranteed to not be publicly routable as they
|
||||
|
/// fall under RFC4193's fc00::/7 subnet allocated to unique-local addresses.
|
||||
|
static const std::array<uint8_t, 6> INTERNAL_IN_IPV6_PREFIX{ |
||||
|
0xFD, 0x6B, 0x88, 0xC0, 0x87, 0x24 // 0xFD + sha256("bitcoin")[0:5].
|
||||
|
}; |
||||
|
|
||||
|
/// Size of IPv4 address (in bytes).
|
||||
|
static constexpr uint64_t ADDR_IPV4_SIZE = 4; |
||||
|
|
||||
|
/// Size of IPv6 address (in bytes).
|
||||
|
static constexpr uint64_t ADDR_IPV6_SIZE = 16; |
||||
|
|
||||
|
/// Size of TORv2 address (in bytes).
|
||||
|
static constexpr uint64_t ADDR_TORV2_SIZE = 10; |
||||
|
|
||||
|
/// Size of TORv3 address (in bytes). This is the length of just the address
|
||||
|
/// as used in BIP155, without the checksum and the version byte.
|
||||
|
static constexpr uint64_t ADDR_TORV3_SIZE = 32; |
||||
|
|
||||
|
/// Size of I2P address (in bytes).
|
||||
|
static constexpr uint64_t ADDR_I2P_SIZE = 32; |
||||
|
|
||||
|
/// Size of CJDNS address (in bytes).
|
||||
|
static constexpr uint64_t ADDR_CJDNS_SIZE = 16; |
||||
|
|
||||
|
/// Size of "internal" (NET_INTERNAL) address (in bytes).
|
||||
|
static constexpr uint64_t ADDR_INTERNAL_SIZE = 10; |
||||
|
|
||||
|
/**
|
||||
|
* Network address. |
||||
|
*/ |
||||
|
class CNetAddr |
||||
|
{ |
||||
|
protected: |
||||
|
/**
|
||||
|
* Raw representation of the network address. |
||||
|
* In network byte order (big endian) for IPv4 and IPv6. |
||||
|
*/ |
||||
|
prevector<ADDR_IPV6_SIZE, uint8_t> m_addr{ADDR_IPV6_SIZE, 0x0}; |
||||
|
|
||||
|
/**
|
||||
|
* Network to which this address belongs. |
||||
|
*/ |
||||
|
Network m_net{NET_IPV6}; |
||||
|
|
||||
|
uint32_t scopeId{0}; // for scoped/link-local ipv6 addresses
|
||||
|
|
||||
|
public: |
||||
|
CNetAddr(); |
||||
|
explicit CNetAddr(const struct in_addr& ipv4Addr); |
||||
|
void SetIP(const CNetAddr& ip); |
||||
|
|
||||
|
private: |
||||
|
/**
|
||||
|
* Set from a legacy IPv6 address. |
||||
|
* Legacy IPv6 address may be a normal IPv6 address, or another address |
||||
|
* (e.g. IPv4) disguised as IPv6. This encoding is used in the legacy |
||||
|
* `addr` encoding. |
||||
|
*/ |
||||
|
void SetLegacyIPv6(Span<const uint8_t> ipv6); |
||||
|
|
||||
|
public: |
||||
|
/** check whether a given address is in a network we can probably connect to */ |
||||
|
bool IsReachableNetwork(); |
||||
|
bool SetInternal(const std::string& name); |
||||
|
|
||||
|
bool SetSpecial(const std::string &strName); // for Tor addresses
|
||||
|
|
||||
|
bool IsBindAny() const; // INADDR_ANY equivalent
|
||||
|
bool IsIPv4() const; // IPv4 mapped address (::FFFF:0:0/96, 0.0.0.0/0)
|
||||
|
bool IsIPv6() const; // IPv6 address (not mapped IPv4, not Tor)
|
||||
|
bool IsRFC1918() const; // IPv4 private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12)
|
||||
|
bool IsRFC2544() const; // IPv4 inter-network communcations (192.18.0.0/15)
|
||||
|
bool IsRFC6598() const; // IPv4 ISP-level NAT (100.64.0.0/10)
|
||||
|
bool IsRFC5737() const; // IPv4 documentation addresses (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24)
|
||||
|
bool IsRFC3849() const; // IPv6 documentation address (2001:0DB8::/32)
|
||||
|
bool IsRFC3927() const; // IPv4 autoconfig (169.254.0.0/16)
|
||||
|
bool IsRFC3964() const; // IPv6 6to4 tunnelling (2002::/16)
|
||||
|
bool IsRFC4193() const; // IPv6 unique local (FC00::/7)
|
||||
|
bool IsRFC4380() const; // IPv6 Teredo tunnelling (2001::/32)
|
||||
|
bool IsRFC4843() const; // IPv6 ORCHID (deprecated) (2001:10::/28)
|
||||
|
bool IsRFC7343() const; // IPv6 ORCHIDv2 (2001:20::/28)
|
||||
|
bool IsRFC4862() const; // IPv6 autoconfig (FE80::/64)
|
||||
|
bool IsRFC6052() const; // IPv6 well-known prefix for IPv4-embedded address (64:FF9B::/96)
|
||||
|
bool IsRFC6145() const; // IPv6 IPv4-translated address (::FFFF:0:0:0/96) (actually defined in RFC2765)
|
||||
|
bool IsHeNet() const; // IPv6 Hurricane Electric - https://he.net (2001:0470::/36)
|
||||
|
bool IsTor() const; |
||||
|
bool IsI2P() const; |
||||
|
bool IsCJDNS() const; |
||||
|
bool IsLocal() const; |
||||
|
bool IsRoutable() const; |
||||
|
bool IsInternal() const; |
||||
|
bool IsValid() const; |
||||
|
|
||||
|
/**
|
||||
|
* Check if the current object can be serialized in pre-ADDRv2/BIP155 format. |
||||
|
*/ |
||||
|
bool IsAddrV1Compatible() const; |
||||
|
|
||||
|
enum Network GetNetwork() const; |
||||
|
std::string ToString() const; |
||||
|
std::string ToStringIP() const; |
||||
|
uint64_t GetHash() const; |
||||
|
bool GetInAddr(struct in_addr* pipv4Addr) const; |
||||
|
uint32_t GetNetClass() const; |
||||
|
|
||||
|
//! For IPv4, mapped IPv4, SIIT translated IPv4, Teredo, 6to4 tunneled addresses, return the relevant IPv4 address as a uint32.
|
||||
|
uint32_t GetLinkedIPv4() const; |
||||
|
//! Whether this address has a linked IPv4 address (see GetLinkedIPv4()).
|
||||
|
bool HasLinkedIPv4() const; |
||||
|
|
||||
|
// The AS on the BGP path to the node we use to diversify
|
||||
|
// peers in AddrMan bucketing based on the AS infrastructure.
|
||||
|
// The ip->AS mapping depends on how asmap is constructed.
|
||||
|
uint32_t GetMappedAS(const std::vector<bool> &asmap) const; |
||||
|
|
||||
|
std::vector<unsigned char> GetGroup(const std::vector<bool> &asmap) const; |
||||
|
std::vector<unsigned char> GetAddrBytes() const; |
||||
|
int GetReachabilityFrom(const CNetAddr *paddrPartner = nullptr) const; |
||||
|
|
||||
|
CNetAddr(const struct in6_addr& pipv6Addr, const uint32_t scope = 0); |
||||
|
bool GetIn6Addr(struct in6_addr* pipv6Addr) const; |
||||
|
|
||||
|
friend bool operator==(const CNetAddr& a, const CNetAddr& b); |
||||
|
friend bool operator!=(const CNetAddr& a, const CNetAddr& b) { return !(a == b); } |
||||
|
friend bool operator<(const CNetAddr& a, const CNetAddr& b); |
||||
|
|
||||
|
/**
|
||||
|
* Serialize to a stream. |
||||
|
*/ |
||||
|
template <typename Stream> |
||||
|
void Serialize(Stream& s) const |
||||
|
{ |
||||
|
if (s.GetVersion() & ADDRV2_FORMAT) { |
||||
|
SerializeV2Stream(s); |
||||
|
} else { |
||||
|
SerializeV1Stream(s); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Unserialize from a stream. |
||||
|
*/ |
||||
|
template <typename Stream> |
||||
|
void Unserialize(Stream& s) |
||||
|
{ |
||||
|
if (s.GetVersion() & ADDRV2_FORMAT) { |
||||
|
UnserializeV2Stream(s); |
||||
|
} else { |
||||
|
UnserializeV1Stream(s); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
friend class CSubNet; |
||||
|
|
||||
|
private: |
||||
|
/**
|
||||
|
* Parse a Tor address and set this object to it. |
||||
|
* @param[in] addr Address to parse, must be a valid C string, for example |
||||
|
* pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion. |
||||
|
* @returns Whether the operation was successful. |
||||
|
* @see CNetAddr::IsTor() |
||||
|
*/ |
||||
|
bool SetTor(const std::string& addr); |
||||
|
|
||||
|
/**
|
||||
|
* Parse an I2P address and set this object to it. |
||||
|
* @param[in] addr Address to parse, must be a valid C string, for example |
||||
|
* ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p. |
||||
|
* @returns Whether the operation was successful. |
||||
|
* @see CNetAddr::IsI2P() |
||||
|
*/ |
||||
|
bool SetI2P(const std::string& addr); |
||||
|
/**
|
||||
|
* BIP155 network ids recognized by this software. |
||||
|
*/ |
||||
|
enum BIP155Network : uint8_t { |
||||
|
IPV4 = 1, |
||||
|
IPV6 = 2, |
||||
|
TORV2 = 3, |
||||
|
TORV3 = 4, |
||||
|
I2P = 5, |
||||
|
CJDNS = 6, |
||||
|
}; |
||||
|
|
||||
|
/**
|
||||
|
* Size of CNetAddr when serialized as ADDRv1 (pre-BIP155) (in bytes). |
||||
|
*/ |
||||
|
static constexpr uint64_t V1_SERIALIZATION_SIZE = ADDR_IPV6_SIZE; |
||||
|
|
||||
|
/**
|
||||
|
* Maximum size of an address as defined in BIP155 (in bytes). |
||||
|
* This is only the size of the address, not the entire CNetAddr object |
||||
|
* when serialized. |
||||
|
*/ |
||||
|
static constexpr uint64_t MAX_ADDRV2_SIZE = 512; |
||||
|
|
||||
|
/**
|
||||
|
* Get the BIP155 network id of this address. |
||||
|
* Must not be called for IsInternal() objects. |
||||
|
* @returns BIP155 network id |
||||
|
*/ |
||||
|
BIP155Network GetBIP155Network() const; |
||||
|
|
||||
|
/**
|
||||
|
* Set `m_net` from the provided BIP155 network id and size after validation. |
||||
|
* @retval true the network was recognized, is valid and `m_net` was set |
||||
|
* @retval false not recognised (from future?) and should be silently ignored |
||||
|
* @throws std::ios_base::failure if the network is one of the BIP155 founding |
||||
|
* networks (id 1..6) with wrong address size. |
||||
|
*/ |
||||
|
bool SetNetFromBIP155Network(uint8_t possible_bip155_net, uint64_t address_size); |
||||
|
|
||||
|
/**
|
||||
|
* Serialize in pre-ADDRv2/BIP155 format to an array. |
||||
|
*/ |
||||
|
void SerializeV1Array(uint8_t (&arr)[V1_SERIALIZATION_SIZE]) const |
||||
|
{ |
||||
|
uint64_t prefix_size; |
||||
|
|
||||
|
switch (m_net) { |
||||
|
case NET_IPV6: |
||||
|
assert(m_addr.size() == sizeof(arr)); |
||||
|
memcpy(arr, m_addr.data(), m_addr.size()); |
||||
|
return; |
||||
|
case NET_IPV4: |
||||
|
prefix_size = sizeof(IPV4_IN_IPV6_PREFIX); |
||||
|
assert(prefix_size + m_addr.size() == sizeof(arr)); |
||||
|
memcpy(arr, IPV4_IN_IPV6_PREFIX.data(), prefix_size); |
||||
|
memcpy(arr + prefix_size, m_addr.data(), m_addr.size()); |
||||
|
return; |
||||
|
case NET_ONION: |
||||
|
if (m_addr.size() == ADDR_TORV3_SIZE) { |
||||
|
break; |
||||
|
} |
||||
|
prefix_size = sizeof(TORV2_IN_IPV6_PREFIX); |
||||
|
assert(prefix_size + m_addr.size() == sizeof(arr)); |
||||
|
memcpy(arr, TORV2_IN_IPV6_PREFIX.data(), prefix_size); |
||||
|
memcpy(arr + prefix_size, m_addr.data(), m_addr.size()); |
||||
|
return; |
||||
|
case NET_INTERNAL: |
||||
|
prefix_size = sizeof(INTERNAL_IN_IPV6_PREFIX); |
||||
|
assert(prefix_size + m_addr.size() == sizeof(arr)); |
||||
|
memcpy(arr, INTERNAL_IN_IPV6_PREFIX.data(), prefix_size); |
||||
|
memcpy(arr + prefix_size, m_addr.data(), m_addr.size()); |
||||
|
return; |
||||
|
case NET_I2P: |
||||
|
break; |
||||
|
case NET_CJDNS: |
||||
|
break; |
||||
|
case NET_UNROUTABLE: |
||||
|
case NET_MAX: |
||||
|
assert(false); |
||||
|
} // no default case, so the compiler can warn about missing cases
|
||||
|
|
||||
|
// Serialize TORv3, I2P and CJDNS as all-zeros.
|
||||
|
memset(arr, 0x0, V1_SERIALIZATION_SIZE); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Serialize in pre-ADDRv2/BIP155 format to a stream. |
||||
|
*/ |
||||
|
template <typename Stream> |
||||
|
void SerializeV1Stream(Stream& s) const |
||||
|
{ |
||||
|
uint8_t serialized[V1_SERIALIZATION_SIZE]; |
||||
|
|
||||
|
SerializeV1Array(serialized); |
||||
|
|
||||
|
s << serialized; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Serialize as ADDRv2 / BIP155. |
||||
|
*/ |
||||
|
template <typename Stream> |
||||
|
void SerializeV2Stream(Stream& s) const |
||||
|
{ |
||||
|
if (IsInternal()) { |
||||
|
// Serialize NET_INTERNAL as embedded in IPv6. We need to
|
||||
|
// serialize such addresses from addrman.
|
||||
|
s << static_cast<uint8_t>(BIP155Network::IPV6); |
||||
|
s << COMPACTSIZE(ADDR_IPV6_SIZE); |
||||
|
SerializeV1Stream(s); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
s << static_cast<uint8_t>(GetBIP155Network()); |
||||
|
s << m_addr; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Unserialize from a pre-ADDRv2/BIP155 format from an array. |
||||
|
*/ |
||||
|
void UnserializeV1Array(uint8_t (&arr)[V1_SERIALIZATION_SIZE]) |
||||
|
{ |
||||
|
// Use SetLegacyIPv6() so that m_net is set correctly. For example
|
||||
|
// ::FFFF:0102:0304 should be set as m_net=NET_IPV4 (1.2.3.4).
|
||||
|
SetLegacyIPv6(arr); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Unserialize from a pre-ADDRv2/BIP155 format from a stream. |
||||
|
*/ |
||||
|
template <typename Stream> |
||||
|
void UnserializeV1Stream(Stream& s) |
||||
|
{ |
||||
|
uint8_t serialized[V1_SERIALIZATION_SIZE]; |
||||
|
|
||||
|
s >> serialized; |
||||
|
|
||||
|
UnserializeV1Array(serialized); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Unserialize from a ADDRv2 / BIP155 format. |
||||
|
*/ |
||||
|
template <typename Stream> |
||||
|
void UnserializeV2Stream(Stream& s) |
||||
|
{ |
||||
|
uint8_t bip155_net; |
||||
|
s >> bip155_net; |
||||
|
|
||||
|
uint64_t address_size; |
||||
|
s >> COMPACTSIZE(address_size); |
||||
|
|
||||
|
if (address_size > MAX_ADDRV2_SIZE) { |
||||
|
throw std::ios_base::failure(strprintf( |
||||
|
"Address too long: %u > %u", address_size, MAX_ADDRV2_SIZE)); |
||||
|
} |
||||
|
|
||||
|
scopeId = 0; |
||||
|
|
||||
|
if (SetNetFromBIP155Network(bip155_net, address_size)) { |
||||
|
m_addr.resize(address_size); |
||||
|
s >> MakeSpan(m_addr); |
||||
|
|
||||
|
if (m_net != NET_IPV6) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Do some special checks on IPv6 addresses.
|
||||
|
|
||||
|
// Recognize NET_INTERNAL embedded in IPv6, such addresses are not
|
||||
|
// gossiped but could be coming from addrman, when unserializing from
|
||||
|
// disk.
|
||||
|
if (HasPrefix(m_addr, INTERNAL_IN_IPV6_PREFIX)) { |
||||
|
m_net = NET_INTERNAL; |
||||
|
memmove(m_addr.data(), m_addr.data() + INTERNAL_IN_IPV6_PREFIX.size(), |
||||
|
ADDR_INTERNAL_SIZE); |
||||
|
m_addr.resize(ADDR_INTERNAL_SIZE); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!HasPrefix(m_addr, IPV4_IN_IPV6_PREFIX) && |
||||
|
!HasPrefix(m_addr, TORV2_IN_IPV6_PREFIX)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// IPv4 and TORv2 are not supposed to be embedded in IPv6 (like in V1
|
||||
|
// encoding). Unserialize as !IsValid(), thus ignoring them.
|
||||
|
} else { |
||||
|
// If we receive an unknown BIP155 network id (from the future?) then
|
||||
|
// ignore the address - unserialize as !IsValid().
|
||||
|
s.ignore(address_size); |
||||
|
} |
||||
|
|
||||
|
// Mimic a default-constructed CNetAddr object which is !IsValid() and thus
|
||||
|
// will not be gossiped, but continue reading next addresses from the stream.
|
||||
|
m_net = NET_IPV6; |
||||
|
m_addr.assign(ADDR_IPV6_SIZE, 0x0); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
class CSubNet |
||||
|
{ |
||||
|
protected: |
||||
|
/// Network (base) address
|
||||
|
CNetAddr network; |
||||
|
/// Netmask, in network byte order
|
||||
|
uint8_t netmask[16]; |
||||
|
/// Is this value valid? (only used to signal parse errors)
|
||||
|
bool valid; |
||||
|
|
||||
|
public: |
||||
|
CSubNet(); |
||||
|
CSubNet(const CNetAddr& addr, uint8_t mask); |
||||
|
CSubNet(const CNetAddr& addr, const CNetAddr& mask); |
||||
|
|
||||
|
//constructor for single ip subnet (<ipv4>/32 or <ipv6>/128)
|
||||
|
explicit CSubNet(const CNetAddr& addr); |
||||
|
|
||||
|
bool Match(const CNetAddr &addr) const; |
||||
|
|
||||
|
std::string ToString() const; |
||||
|
bool IsValid() const; |
||||
|
|
||||
|
friend bool operator==(const CSubNet& a, const CSubNet& b); |
||||
|
friend bool operator!=(const CSubNet& a, const CSubNet& b) { return !(a == b); } |
||||
|
friend bool operator<(const CSubNet& a, const CSubNet& b); |
||||
|
|
||||
|
ADD_SERIALIZE_METHODS; |
||||
|
|
||||
|
template <typename Stream, typename Operation> |
||||
|
inline void SerializationOp(Stream& s, Operation ser_action) { |
||||
|
READWRITE(network); |
||||
|
READWRITE(FLATDATA(netmask)); |
||||
|
READWRITE(FLATDATA(valid)); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** A combination of a network address (CNetAddr) and a (TCP) port */ |
||||
|
class CService : public CNetAddr |
||||
|
{ |
||||
|
protected: |
||||
|
unsigned short port; // host order
|
||||
|
|
||||
|
public: |
||||
|
CService(); |
||||
|
CService(const CNetAddr& ip, unsigned short port); |
||||
|
CService(const struct in_addr& ipv4Addr, unsigned short port); |
||||
|
explicit CService(const struct sockaddr_in& addr); |
||||
|
unsigned short GetPort() const; |
||||
|
bool GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const; |
||||
|
bool SetSockAddr(const struct sockaddr* paddr); |
||||
|
friend bool operator==(const CService& a, const CService& b); |
||||
|
friend bool operator!=(const CService& a, const CService& b) { return !(a == b); } |
||||
|
friend bool operator<(const CService& a, const CService& b); |
||||
|
std::vector<unsigned char> GetKey() const; |
||||
|
std::string ToString() const; |
||||
|
std::string ToStringPort() const; |
||||
|
std::string ToStringIPPort() const; |
||||
|
|
||||
|
CService(const struct in6_addr& ipv6Addr, unsigned short port); |
||||
|
CService(const struct sockaddr_in6& addr); |
||||
|
|
||||
|
SERIALIZE_METHODS(CService, obj) |
||||
|
{ |
||||
|
READWRITEAS(CNetAddr, obj); |
||||
|
READ_WRITE(Using<BigEndianFormatter<2>>(obj.port)); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
bool SanityCheckASMap(const std::vector<bool>& asmap); |
||||
|
|
||||
|
#endif // HUSH_NETADDRESS_H
|
File diff suppressed because it is too large
@ -0,0 +1,37 @@ |
|||||
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
|
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef HUSH_NETMESSAGEMAKER_H |
||||
|
#define HUSH_NETMESSAGEMAKER_H |
||||
|
|
||||
|
#include <net.h> |
||||
|
#include <serialize.h> |
||||
|
|
||||
|
class CNetMsgMaker |
||||
|
{ |
||||
|
public: |
||||
|
explicit CNetMsgMaker(int nVersionIn) : nVersion(nVersionIn){} |
||||
|
|
||||
|
template <typename... Args> |
||||
|
CSerializedNetMsg Make(int nFlags, std::string msg_type, Args&&... args) const |
||||
|
{ |
||||
|
CSerializedNetMsg msg; |
||||
|
msg.m_type = std::move(msg_type); |
||||
|
CVectorWriter{ SER_NETWORK, nFlags | nVersion, msg.data, 0, std::forward<Args>(args)... }; |
||||
|
return msg; |
||||
|
} |
||||
|
|
||||
|
template <typename... Args> |
||||
|
CSerializedNetMsg Make(std::string msg_type, Args&&... args) const |
||||
|
{ |
||||
|
return Make(0, std::move(msg_type), std::forward<Args>(args)...); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
const int nVersion; |
||||
|
}; |
||||
|
|
||||
|
#endif // HUSH_NETMESSAGEMAKER_H
|
@ -0,0 +1,252 @@ |
|||||
|
// Copyright (c) 2018-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef BITCOIN_SPAN_H |
||||
|
#define BITCOIN_SPAN_H |
||||
|
|
||||
|
#include <type_traits> |
||||
|
#include <cstddef> |
||||
|
#include <algorithm> |
||||
|
#include <assert.h> |
||||
|
|
||||
|
#ifdef DEBUG |
||||
|
#define CONSTEXPR_IF_NOT_DEBUG |
||||
|
#define ASSERT_IF_DEBUG(x) assert((x)) |
||||
|
#else |
||||
|
#define CONSTEXPR_IF_NOT_DEBUG constexpr |
||||
|
#define ASSERT_IF_DEBUG(x) |
||||
|
#endif |
||||
|
|
||||
|
#if defined(__clang__) |
||||
|
#if __has_attribute(lifetimebound) |
||||
|
#define SPAN_ATTR_LIFETIMEBOUND [[clang::lifetimebound]] |
||||
|
#else |
||||
|
#define SPAN_ATTR_LIFETIMEBOUND |
||||
|
#endif |
||||
|
#else |
||||
|
#define SPAN_ATTR_LIFETIMEBOUND |
||||
|
#endif |
||||
|
|
||||
|
/** A Span is an object that can refer to a contiguous sequence of objects.
|
||||
|
* |
||||
|
* It implements a subset of C++20's std::span. |
||||
|
* |
||||
|
* Things to be aware of when writing code that deals with Spans: |
||||
|
* |
||||
|
* - Similar to references themselves, Spans are subject to reference lifetime |
||||
|
* issues. The user is responsible for making sure the objects pointed to by |
||||
|
* a Span live as long as the Span is used. For example: |
||||
|
* |
||||
|
* std::vector<int> vec{1,2,3,4}; |
||||
|
* Span<int> sp(vec); |
||||
|
* vec.push_back(5); |
||||
|
* printf("%i\n", sp.front()); // UB!
|
||||
|
* |
||||
|
* may exhibit undefined behavior, as increasing the size of a vector may |
||||
|
* invalidate references. |
||||
|
* |
||||
|
* - One particular pitfall is that Spans can be constructed from temporaries, |
||||
|
* but this is unsafe when the Span is stored in a variable, outliving the |
||||
|
* temporary. For example, this will compile, but exhibits undefined behavior: |
||||
|
* |
||||
|
* Span<const int> sp(std::vector<int>{1, 2, 3}); |
||||
|
* printf("%i\n", sp.front()); // UB!
|
||||
|
* |
||||
|
* The lifetime of the vector ends when the statement it is created in ends. |
||||
|
* Thus the Span is left with a dangling reference, and using it is undefined. |
||||
|
* |
||||
|
* - Due to Span's automatic creation from range-like objects (arrays, and data |
||||
|
* types that expose a data() and size() member function), functions that |
||||
|
* accept a Span as input parameter can be called with any compatible |
||||
|
* range-like object. For example, this works: |
||||
|
* |
||||
|
* void Foo(Span<const int> arg); |
||||
|
* |
||||
|
* Foo(std::vector<int>{1, 2, 3}); // Works
|
||||
|
* |
||||
|
* This is very useful in cases where a function truly does not care about the |
||||
|
* container, and only about having exactly a range of elements. However it |
||||
|
* may also be surprising to see automatic conversions in this case. |
||||
|
* |
||||
|
* When a function accepts a Span with a mutable element type, it will not |
||||
|
* accept temporaries; only variables or other references. For example: |
||||
|
* |
||||
|
* void FooMut(Span<int> arg); |
||||
|
* |
||||
|
* FooMut(std::vector<int>{1, 2, 3}); // Does not compile
|
||||
|
* std::vector<int> baz{1, 2, 3}; |
||||
|
* FooMut(baz); // Works
|
||||
|
* |
||||
|
* This is similar to how functions that take (non-const) lvalue references |
||||
|
* as input cannot accept temporaries. This does not work either: |
||||
|
* |
||||
|
* void FooVec(std::vector<int>& arg); |
||||
|
* FooVec(std::vector<int>{1, 2, 3}); // Does not compile
|
||||
|
* |
||||
|
* The idea is that if a function accepts a mutable reference, a meaningful |
||||
|
* result will be present in that variable after the call. Passing a temporary |
||||
|
* is useless in that context. |
||||
|
*/ |
||||
|
template<typename C> |
||||
|
class Span |
||||
|
{ |
||||
|
C* m_data; |
||||
|
std::size_t m_size; |
||||
|
|
||||
|
template <class T> |
||||
|
struct is_Span_int : public std::false_type {}; |
||||
|
template <class T> |
||||
|
struct is_Span_int<Span<T>> : public std::true_type {}; |
||||
|
template <class T> |
||||
|
struct is_Span : public is_Span_int<typename std::remove_cv<T>::type>{}; |
||||
|
|
||||
|
|
||||
|
public: |
||||
|
constexpr Span() noexcept : m_data(nullptr), m_size(0) {} |
||||
|
|
||||
|
/** Construct a span from a begin pointer and a size.
|
||||
|
* |
||||
|
* This implements a subset of the iterator-based std::span constructor in C++20, |
||||
|
* which is hard to implement without std::address_of. |
||||
|
*/ |
||||
|
template <typename T, typename std::enable_if<std::is_convertible<T (*)[], C (*)[]>::value, int>::type = 0> |
||||
|
constexpr Span(T* begin, std::size_t size) noexcept : m_data(begin), m_size(size) {} |
||||
|
|
||||
|
/** Construct a span from a begin and end pointer.
|
||||
|
* |
||||
|
* This implements a subset of the iterator-based std::span constructor in C++20, |
||||
|
* which is hard to implement without std::address_of. |
||||
|
*/ |
||||
|
template <typename T, typename std::enable_if<std::is_convertible<T (*)[], C (*)[]>::value, int>::type = 0> |
||||
|
CONSTEXPR_IF_NOT_DEBUG Span(T* begin, T* end) noexcept : m_data(begin), m_size(end - begin) |
||||
|
{ |
||||
|
ASSERT_IF_DEBUG(end >= begin); |
||||
|
} |
||||
|
|
||||
|
/** Implicit conversion of spans between compatible types.
|
||||
|
* |
||||
|
* Specifically, if a pointer to an array of type O can be implicitly converted to a pointer to an array of type |
||||
|
* C, then permit implicit conversion of Span<O> to Span<C>. This matches the behavior of the corresponding |
||||
|
* C++20 std::span constructor. |
||||
|
* |
||||
|
* For example this means that a Span<T> can be converted into a Span<const T>. |
||||
|
*/ |
||||
|
template <typename O, typename std::enable_if<std::is_convertible<O (*)[], C (*)[]>::value, int>::type = 0> |
||||
|
constexpr Span(const Span<O>& other) noexcept : m_data(other.m_data), m_size(other.m_size) {} |
||||
|
|
||||
|
/** Default copy constructor. */ |
||||
|
constexpr Span(const Span&) noexcept = default; |
||||
|
|
||||
|
/** Default assignment operator. */ |
||||
|
Span& operator=(const Span& other) noexcept = default; |
||||
|
|
||||
|
/** Construct a Span from an array. This matches the corresponding C++20 std::span constructor. */ |
||||
|
template <int N> |
||||
|
constexpr Span(C (&a)[N]) noexcept : m_data(a), m_size(N) {} |
||||
|
|
||||
|
/** Construct a Span for objects with .data() and .size() (std::string, std::array, std::vector, ...).
|
||||
|
* |
||||
|
* This implements a subset of the functionality provided by the C++20 std::span range-based constructor. |
||||
|
* |
||||
|
* To prevent surprises, only Spans for constant value types are supported when passing in temporaries. |
||||
|
* Note that this restriction does not exist when converting arrays or other Spans (see above). |
||||
|
*/ |
||||
|
template <typename V> |
||||
|
constexpr Span(V& other SPAN_ATTR_LIFETIMEBOUND, |
||||
|
typename std::enable_if<!is_Span<V>::value && |
||||
|
std::is_convertible<typename std::remove_pointer<decltype(std::declval<V&>().data())>::type (*)[], C (*)[]>::value && |
||||
|
std::is_convertible<decltype(std::declval<V&>().size()), std::size_t>::value, std::nullptr_t>::type = nullptr) |
||||
|
: m_data(other.data()), m_size(other.size()){} |
||||
|
|
||||
|
template <typename V> |
||||
|
constexpr Span(const V& other SPAN_ATTR_LIFETIMEBOUND, |
||||
|
typename std::enable_if<!is_Span<V>::value && |
||||
|
std::is_convertible<typename std::remove_pointer<decltype(std::declval<const V&>().data())>::type (*)[], C (*)[]>::value && |
||||
|
std::is_convertible<decltype(std::declval<const V&>().size()), std::size_t>::value, std::nullptr_t>::type = nullptr) |
||||
|
: m_data(other.data()), m_size(other.size()){} |
||||
|
|
||||
|
constexpr C* data() const noexcept { return m_data; } |
||||
|
constexpr C* begin() const noexcept { return m_data; } |
||||
|
constexpr C* end() const noexcept { return m_data + m_size; } |
||||
|
CONSTEXPR_IF_NOT_DEBUG C& front() const noexcept |
||||
|
{ |
||||
|
ASSERT_IF_DEBUG(size() > 0); |
||||
|
return m_data[0]; |
||||
|
} |
||||
|
CONSTEXPR_IF_NOT_DEBUG C& back() const noexcept |
||||
|
{ |
||||
|
ASSERT_IF_DEBUG(size() > 0); |
||||
|
return m_data[m_size - 1]; |
||||
|
} |
||||
|
constexpr std::size_t size() const noexcept { return m_size; } |
||||
|
constexpr bool empty() const noexcept { return size() == 0; } |
||||
|
CONSTEXPR_IF_NOT_DEBUG C& operator[](std::size_t pos) const noexcept |
||||
|
{ |
||||
|
ASSERT_IF_DEBUG(size() > pos); |
||||
|
return m_data[pos]; |
||||
|
} |
||||
|
CONSTEXPR_IF_NOT_DEBUG Span<C> subspan(std::size_t offset) const noexcept |
||||
|
{ |
||||
|
ASSERT_IF_DEBUG(size() >= offset); |
||||
|
return Span<C>(m_data + offset, m_size - offset); |
||||
|
} |
||||
|
CONSTEXPR_IF_NOT_DEBUG Span<C> subspan(std::size_t offset, std::size_t count) const noexcept |
||||
|
{ |
||||
|
ASSERT_IF_DEBUG(size() >= offset + count); |
||||
|
return Span<C>(m_data + offset, count); |
||||
|
} |
||||
|
CONSTEXPR_IF_NOT_DEBUG Span<C> first(std::size_t count) const noexcept |
||||
|
{ |
||||
|
ASSERT_IF_DEBUG(size() >= count); |
||||
|
return Span<C>(m_data, count); |
||||
|
} |
||||
|
CONSTEXPR_IF_NOT_DEBUG Span<C> last(std::size_t count) const noexcept |
||||
|
{ |
||||
|
ASSERT_IF_DEBUG(size() >= count); |
||||
|
return Span<C>(m_data + m_size - count, count); |
||||
|
} |
||||
|
|
||||
|
friend constexpr bool operator==(const Span& a, const Span& b) noexcept { return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); } |
||||
|
friend constexpr bool operator!=(const Span& a, const Span& b) noexcept { return !(a == b); } |
||||
|
friend constexpr bool operator<(const Span& a, const Span& b) noexcept { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); } |
||||
|
friend constexpr bool operator<=(const Span& a, const Span& b) noexcept { return !(b < a); } |
||||
|
friend constexpr bool operator>(const Span& a, const Span& b) noexcept { return (b < a); } |
||||
|
friend constexpr bool operator>=(const Span& a, const Span& b) noexcept { return !(a < b); } |
||||
|
|
||||
|
template <typename O> friend class Span; |
||||
|
}; |
||||
|
|
||||
|
// MakeSpan helps constructing a Span of the right type automatically.
|
||||
|
/** MakeSpan for arrays: */ |
||||
|
template <typename A, int N> Span<A> constexpr MakeSpan(A (&a)[N]) { return Span<A>(a, N); } |
||||
|
/** MakeSpan for temporaries / rvalue references, only supporting const output. */ |
||||
|
template <typename V> constexpr auto MakeSpan(V&& v SPAN_ATTR_LIFETIMEBOUND) -> typename std::enable_if<!std::is_lvalue_reference<V>::value, Span<const typename std::remove_pointer<decltype(v.data())>::type>>::type { return std::forward<V>(v); } |
||||
|
/** MakeSpan for (lvalue) references, supporting mutable output. */ |
||||
|
template <typename V> constexpr auto MakeSpan(V& v SPAN_ATTR_LIFETIMEBOUND) -> Span<typename std::remove_pointer<decltype(v.data())>::type> { return v; } |
||||
|
|
||||
|
/** Pop the last element off a span, and return a reference to that element. */ |
||||
|
template <typename T> |
||||
|
T& SpanPopBack(Span<T>& span) |
||||
|
{ |
||||
|
size_t size = span.size(); |
||||
|
ASSERT_IF_DEBUG(size > 0); |
||||
|
T& back = span[size - 1]; |
||||
|
span = Span<T>(span.data(), size - 1); |
||||
|
return back; |
||||
|
} |
||||
|
|
||||
|
// Helper functions to safely cast to unsigned char pointers.
|
||||
|
inline unsigned char* UCharCast(char* c) { return (unsigned char*)c; } |
||||
|
inline unsigned char* UCharCast(unsigned char* c) { return c; } |
||||
|
inline const unsigned char* UCharCast(const char* c) { return (unsigned char*)c; } |
||||
|
inline const unsigned char* UCharCast(const unsigned char* c) { return c; } |
||||
|
|
||||
|
// Helper function to safely convert a Span to a Span<[const] unsigned char>.
|
||||
|
template <typename T> constexpr auto UCharSpanCast(Span<T> s) -> Span<typename std::remove_pointer<decltype(UCharCast(s.data()))>::type> { return {UCharCast(s.data()), s.size()}; } |
||||
|
|
||||
|
/** Like MakeSpan, but for (const) unsigned char member types only. Only works for (un)signed char containers. */ |
||||
|
template <typename V> constexpr auto MakeUCharSpan(V&& v) -> decltype(UCharSpanCast(MakeSpan(std::forward<V>(v)))) { return UCharSpanCast(MakeSpan(std::forward<V>(v))); } |
||||
|
|
||||
|
#endif |
@ -0,0 +1,47 @@ |
|||||
|
// Copyright (c) 2015-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2017 The Zcash developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#include <fs.h> |
||||
|
#include <limits> |
||||
|
#include <stdio.h> |
||||
|
#include <string> |
||||
|
#include <utility> |
||||
|
|
||||
|
std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()) |
||||
|
{ |
||||
|
FILE *f = fsbridge::fopen(filename, "rb"); |
||||
|
if (f == nullptr) |
||||
|
return std::make_pair(false,""); |
||||
|
std::string retval; |
||||
|
char buffer[128]; |
||||
|
do { |
||||
|
const size_t n = fread(buffer, 1, sizeof(buffer), f); |
||||
|
// Check for reading errors so we don't return any data if we couldn't
|
||||
|
// read the entire file (or up to maxsize)
|
||||
|
if (ferror(f)) { |
||||
|
fclose(f); |
||||
|
return std::make_pair(false,""); |
||||
|
} |
||||
|
retval.append(buffer, buffer+n); |
||||
|
} while (!feof(f) && retval.size() <= maxsize); |
||||
|
fclose(f); |
||||
|
return std::make_pair(true,retval); |
||||
|
} |
||||
|
|
||||
|
bool WriteBinaryFile(const fs::path &filename, const std::string &data) |
||||
|
{ |
||||
|
FILE *f = fsbridge::fopen(filename, "wb"); |
||||
|
if (f == nullptr) |
||||
|
return false; |
||||
|
if (fwrite(data.data(), 1, data.size(), f) != data.size()) { |
||||
|
fclose(f); |
||||
|
return false; |
||||
|
} |
||||
|
if (fclose(f) != 0) { |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
// Copyright (c) 2015-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef BITCOIN_UTIL_READWRITEFILE_H |
||||
|
#define BITCOIN_UTIL_READWRITEFILE_H |
||||
|
|
||||
|
#include <fs.h> |
||||
|
|
||||
|
#include <limits> |
||||
|
#include <string> |
||||
|
#include <utility> |
||||
|
|
||||
|
/** Read full contents of a file and return them in a std::string.
|
||||
|
* Returns a pair <status, string>. |
||||
|
* If an error occurred, status will be false, otherwise status will be true and the data will be returned in string. |
||||
|
* |
||||
|
* @param maxsize Puts a maximum size limit on the file that is read. If the file is larger than this, truncated data |
||||
|
* (with len > maxsize) will be returned. |
||||
|
*/ |
||||
|
std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()); |
||||
|
|
||||
|
/** Write contents of std::string to a file.
|
||||
|
* @return true on success. |
||||
|
*/ |
||||
|
bool WriteBinaryFile(const fs::path &filename, const std::string &data); |
||||
|
|
||||
|
#endif /* BITCOIN_UTIL_READWRITEFILE_H */ |
@ -0,0 +1,328 @@ |
|||||
|
// Copyright (c) 2020-2021 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#include <compat.h> |
||||
|
#include <tinyformat.h> |
||||
|
#include <util/sock.h> |
||||
|
#include <utiltime.h> |
||||
|
#include <util.h> |
||||
|
#include <codecvt> |
||||
|
#include <cwchar> |
||||
|
#include <locale> |
||||
|
#include <stdexcept> |
||||
|
#include <string> |
||||
|
|
||||
|
#ifdef USE_POLL |
||||
|
#include <poll.h> |
||||
|
#endif |
||||
|
|
||||
|
static inline bool IOErrorIsPermanent(int err) |
||||
|
{ |
||||
|
return err != WSAEAGAIN && err != WSAEINTR && err != WSAEWOULDBLOCK && err != WSAEINPROGRESS; |
||||
|
} |
||||
|
|
||||
|
Sock::Sock() : m_socket(INVALID_SOCKET) {} |
||||
|
|
||||
|
Sock::Sock(SOCKET s) : m_socket(s) {} |
||||
|
|
||||
|
Sock::Sock(Sock&& other) |
||||
|
{ |
||||
|
m_socket = other.m_socket; |
||||
|
other.m_socket = INVALID_SOCKET; |
||||
|
} |
||||
|
|
||||
|
Sock::~Sock() { Reset(); } |
||||
|
|
||||
|
Sock& Sock::operator=(Sock&& other) |
||||
|
{ |
||||
|
Reset(); |
||||
|
m_socket = other.m_socket; |
||||
|
other.m_socket = INVALID_SOCKET; |
||||
|
return *this; |
||||
|
} |
||||
|
|
||||
|
SOCKET Sock::Get() const { return m_socket; } |
||||
|
|
||||
|
SOCKET Sock::Release() |
||||
|
{ |
||||
|
const SOCKET s = m_socket; |
||||
|
m_socket = INVALID_SOCKET; |
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
void Sock::Reset() { CloseSocket(m_socket); } |
||||
|
|
||||
|
ssize_t Sock::Send(const void* data, size_t len, int flags) const |
||||
|
{ |
||||
|
return send(m_socket, static_cast<const char*>(data), len, flags); |
||||
|
} |
||||
|
|
||||
|
ssize_t Sock::Recv(void* buf, size_t len, int flags) const |
||||
|
{ |
||||
|
return recv(m_socket, static_cast<char*>(buf), len, flags); |
||||
|
} |
||||
|
|
||||
|
int Sock::Connect(const sockaddr* addr, socklen_t addr_len) const |
||||
|
{ |
||||
|
return connect(m_socket, addr, addr_len); |
||||
|
} |
||||
|
|
||||
|
int Sock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const |
||||
|
{ |
||||
|
return getsockopt(m_socket, level, opt_name, static_cast<char*>(opt_val), opt_len); |
||||
|
} |
||||
|
|
||||
|
bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const |
||||
|
{ |
||||
|
#ifdef USE_POLL |
||||
|
pollfd fd; |
||||
|
fd.fd = m_socket; |
||||
|
fd.events = 0; |
||||
|
if (requested & RECV) { |
||||
|
fd.events |= POLLIN; |
||||
|
} |
||||
|
if (requested & SEND) { |
||||
|
fd.events |= POLLOUT; |
||||
|
} |
||||
|
|
||||
|
if (poll(&fd, 1, count_milliseconds(timeout)) == SOCKET_ERROR) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (occurred != nullptr) { |
||||
|
*occurred = 0; |
||||
|
if (fd.revents & POLLIN) { |
||||
|
*occurred |= RECV; |
||||
|
} |
||||
|
if (fd.revents & POLLOUT) { |
||||
|
*occurred |= SEND; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
#else |
||||
|
if (!IsSelectableSocket(m_socket)) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
fd_set fdset_recv; |
||||
|
fd_set fdset_send; |
||||
|
FD_ZERO(&fdset_recv); |
||||
|
FD_ZERO(&fdset_send); |
||||
|
|
||||
|
if (requested & RECV) { |
||||
|
FD_SET(m_socket, &fdset_recv); |
||||
|
} |
||||
|
|
||||
|
if (requested & SEND) { |
||||
|
FD_SET(m_socket, &fdset_send); |
||||
|
} |
||||
|
|
||||
|
timeval timeout_struct = MillisToTimeval(timeout.count()); |
||||
|
|
||||
|
if (select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) == SOCKET_ERROR) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (occurred != nullptr) { |
||||
|
*occurred = 0; |
||||
|
if (FD_ISSET(m_socket, &fdset_recv)) { |
||||
|
*occurred |= RECV; |
||||
|
} |
||||
|
if (FD_ISSET(m_socket, &fdset_send)) { |
||||
|
*occurred |= SEND; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
#endif /* USE_POLL */ |
||||
|
} |
||||
|
|
||||
|
void Sock::SendComplete(const std::string& data, |
||||
|
std::chrono::milliseconds timeout) const |
||||
|
{ |
||||
|
const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; |
||||
|
size_t sent{0}; |
||||
|
|
||||
|
for (;;) { |
||||
|
const ssize_t ret{Send(data.data() + sent, data.size() - sent, MSG_NOSIGNAL)}; |
||||
|
|
||||
|
if (ret > 0) { |
||||
|
sent += static_cast<size_t>(ret); |
||||
|
if (sent == data.size()) { |
||||
|
break; |
||||
|
} |
||||
|
} else { |
||||
|
const int err{WSAGetLastError()}; |
||||
|
if (IOErrorIsPermanent(err)) { |
||||
|
throw std::runtime_error(strprintf("send(): %s", NetworkErrorString(err))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const auto now = GetTime<std::chrono::milliseconds>(); |
||||
|
|
||||
|
if (now >= deadline) { |
||||
|
throw std::runtime_error(strprintf( |
||||
|
"Send timeout (sent only %u of %u bytes before that)", sent, data.size())); |
||||
|
} |
||||
|
|
||||
|
// Wait for a short while (or the socket to become ready for sending) before retrying
|
||||
|
// if nothing was sent.
|
||||
|
const auto wait_time = std::min(deadline - now, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); |
||||
|
(void)Wait(wait_time, SEND); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
std::string Sock::RecvUntilTerminator(uint8_t terminator, |
||||
|
std::chrono::milliseconds timeout, |
||||
|
size_t max_data) const |
||||
|
{ |
||||
|
const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; |
||||
|
std::string data; |
||||
|
bool terminator_found{false}; |
||||
|
|
||||
|
// We must not consume any bytes past the terminator from the socket.
|
||||
|
// One option is to read one byte at a time and check if we have read a terminator.
|
||||
|
// However that is very slow. Instead, we peek at what is in the socket and only read
|
||||
|
// as many bytes as possible without crossing the terminator.
|
||||
|
// Reading 64 MiB of random data with 262526 terminator chars takes 37 seconds to read
|
||||
|
// one byte at a time VS 0.71 seconds with the "peek" solution below. Reading one byte
|
||||
|
// at a time is about 50 times slower.
|
||||
|
|
||||
|
for (;;) { |
||||
|
if (data.size() >= max_data) { |
||||
|
throw std::runtime_error( |
||||
|
strprintf("Received too many bytes without a terminator (%u)", data.size())); |
||||
|
} |
||||
|
|
||||
|
char buf[512]; |
||||
|
|
||||
|
const ssize_t peek_ret{Recv(buf, std::min(sizeof(buf), max_data - data.size()), MSG_PEEK)}; |
||||
|
|
||||
|
switch (peek_ret) { |
||||
|
case -1: { |
||||
|
const int err{WSAGetLastError()}; |
||||
|
if (IOErrorIsPermanent(err)) { |
||||
|
throw std::runtime_error(strprintf("recv(): %s", NetworkErrorString(err))); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case 0: |
||||
|
throw std::runtime_error("Connection unexpectedly closed by peer"); |
||||
|
default: |
||||
|
auto end = buf + peek_ret; |
||||
|
auto terminator_pos = std::find(buf, end, terminator); |
||||
|
terminator_found = terminator_pos != end; |
||||
|
|
||||
|
const size_t try_len{terminator_found ? terminator_pos - buf + 1 : |
||||
|
static_cast<size_t>(peek_ret)}; |
||||
|
|
||||
|
const ssize_t read_ret{Recv(buf, try_len, 0)}; |
||||
|
|
||||
|
if (read_ret < 0 || static_cast<size_t>(read_ret) != try_len) { |
||||
|
throw std::runtime_error( |
||||
|
strprintf("recv() returned %u bytes on attempt to read %u bytes but previous " |
||||
|
"peek claimed %u bytes are available", |
||||
|
read_ret, try_len, peek_ret)); |
||||
|
} |
||||
|
|
||||
|
// Don't include the terminator in the output.
|
||||
|
const size_t append_len{terminator_found ? try_len - 1 : try_len}; |
||||
|
|
||||
|
data.append(buf, buf + append_len); |
||||
|
|
||||
|
if (terminator_found) { |
||||
|
return data; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const auto now = GetTime<std::chrono::milliseconds>(); |
||||
|
|
||||
|
if (now >= deadline) { |
||||
|
throw std::runtime_error(strprintf( |
||||
|
"Receive timeout (received %u bytes without terminator before that)", data.size())); |
||||
|
} |
||||
|
|
||||
|
// Wait for a short while (or the socket to become ready for reading) before retrying.
|
||||
|
const auto wait_time = std::min(deadline - now, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); |
||||
|
(void)Wait(wait_time, RECV); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool Sock::IsConnected(std::string& errmsg) const |
||||
|
{ |
||||
|
if (m_socket == INVALID_SOCKET) { |
||||
|
errmsg = "not connected"; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
char c; |
||||
|
switch (Recv(&c, sizeof(c), MSG_PEEK)) { |
||||
|
case -1: { |
||||
|
const int err = WSAGetLastError(); |
||||
|
if (IOErrorIsPermanent(err)) { |
||||
|
errmsg = NetworkErrorString(err); |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
case 0: |
||||
|
errmsg = "closed"; |
||||
|
return false; |
||||
|
default: |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#ifdef WIN32 |
||||
|
std::string NetworkErrorString(int err) |
||||
|
{ |
||||
|
wchar_t buf[256]; |
||||
|
buf[0] = 0; |
||||
|
if(FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, |
||||
|
nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
||||
|
buf, ARRAYSIZE(buf), nullptr)) |
||||
|
{ |
||||
|
return strprintf("%s (%d)", std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf), err); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return strprintf("Unknown error (%d)", err); |
||||
|
} |
||||
|
} |
||||
|
#else |
||||
|
std::string NetworkErrorString(int err) |
||||
|
{ |
||||
|
char buf[256]; |
||||
|
buf[0] = 0; |
||||
|
/* Too bad there are two incompatible implementations of the
|
||||
|
* thread-safe strerror. */ |
||||
|
const char *s; |
||||
|
#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ |
||||
|
s = strerror_r(err, buf, sizeof(buf)); |
||||
|
#else /* POSIX variant always returns message in buffer */ |
||||
|
s = buf; |
||||
|
if (strerror_r(err, buf, sizeof(buf))) |
||||
|
buf[0] = 0; |
||||
|
#endif |
||||
|
return strprintf("%s (%d)", s, err); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
bool CloseSocket(SOCKET& hSocket) |
||||
|
{ |
||||
|
if (hSocket == INVALID_SOCKET) |
||||
|
return false; |
||||
|
#ifdef WIN32 |
||||
|
int ret = closesocket(hSocket); |
||||
|
#else |
||||
|
int ret = close(hSocket); |
||||
|
#endif |
||||
|
if (ret) { |
||||
|
LogPrintf("Socket close failed: %d. Error: %s\n", hSocket, NetworkErrorString(WSAGetLastError())); |
||||
|
} |
||||
|
hSocket = INVALID_SOCKET; |
||||
|
return ret != SOCKET_ERROR; |
||||
|
} |
@ -0,0 +1,184 @@ |
|||||
|
// Copyright (c) 2020-2021 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef BITCOIN_UTIL_SOCK_H |
||||
|
#define BITCOIN_UTIL_SOCK_H |
||||
|
|
||||
|
#include <compat.h> |
||||
|
// #include <threadinterrupt.h>
|
||||
|
#include <utiltime.h> |
||||
|
|
||||
|
#include <chrono> |
||||
|
#include <string> |
||||
|
|
||||
|
/**
|
||||
|
* Maximum time to wait for I/O readiness. |
||||
|
* It will take up until this time to break off in case of an interruption. |
||||
|
*/ |
||||
|
static constexpr auto MAX_WAIT_FOR_IO = 1000; |
||||
|
|
||||
|
/**
|
||||
|
* RAII helper class that manages a socket. Mimics `std::unique_ptr`, but instead of a pointer it |
||||
|
* contains a socket and closes it automatically when it goes out of scope. |
||||
|
*/ |
||||
|
class Sock |
||||
|
{ |
||||
|
public: |
||||
|
/**
|
||||
|
* Default constructor, creates an empty object that does nothing when destroyed. |
||||
|
*/ |
||||
|
Sock(); |
||||
|
|
||||
|
/**
|
||||
|
* Take ownership of an existent socket. |
||||
|
*/ |
||||
|
explicit Sock(SOCKET s); |
||||
|
|
||||
|
/**
|
||||
|
* Copy constructor, disabled because closing the same socket twice is undesirable. |
||||
|
*/ |
||||
|
Sock(const Sock&) = delete; |
||||
|
|
||||
|
/**
|
||||
|
* Move constructor, grab the socket from another object and close ours (if set). |
||||
|
*/ |
||||
|
Sock(Sock&& other); |
||||
|
|
||||
|
/**
|
||||
|
* Destructor, close the socket or do nothing if empty. |
||||
|
*/ |
||||
|
virtual ~Sock(); |
||||
|
|
||||
|
/**
|
||||
|
* Copy assignment operator, disabled because closing the same socket twice is undesirable. |
||||
|
*/ |
||||
|
Sock& operator=(const Sock&) = delete; |
||||
|
|
||||
|
/**
|
||||
|
* Move assignment operator, grab the socket from another object and close ours (if set). |
||||
|
*/ |
||||
|
virtual Sock& operator=(Sock&& other); |
||||
|
|
||||
|
/**
|
||||
|
* Get the value of the contained socket. |
||||
|
* @return socket or INVALID_SOCKET if empty |
||||
|
*/ |
||||
|
virtual SOCKET Get() const; |
||||
|
|
||||
|
/**
|
||||
|
* Get the value of the contained socket and drop ownership. It will not be closed by the |
||||
|
* destructor after this call. |
||||
|
* @return socket or INVALID_SOCKET if empty |
||||
|
*/ |
||||
|
virtual SOCKET Release(); |
||||
|
|
||||
|
/**
|
||||
|
* Close if non-empty. |
||||
|
*/ |
||||
|
virtual void Reset(); |
||||
|
|
||||
|
/**
|
||||
|
* send(2) wrapper. Equivalent to `send(this->Get(), data, len, flags);`. Code that uses this |
||||
|
* wrapper can be unit tested if this method is overridden by a mock Sock implementation. |
||||
|
*/ |
||||
|
virtual ssize_t Send(const void* data, size_t len, int flags) const; |
||||
|
|
||||
|
/**
|
||||
|
* recv(2) wrapper. Equivalent to `recv(this->Get(), buf, len, flags);`. Code that uses this |
||||
|
* wrapper can be unit tested if this method is overridden by a mock Sock implementation. |
||||
|
*/ |
||||
|
virtual ssize_t Recv(void* buf, size_t len, int flags) const; |
||||
|
|
||||
|
/**
|
||||
|
* connect(2) wrapper. Equivalent to `connect(this->Get(), addr, addrlen)`. Code that uses this |
||||
|
* wrapper can be unit tested if this method is overridden by a mock Sock implementation. |
||||
|
*/ |
||||
|
virtual int Connect(const sockaddr* addr, socklen_t addr_len) const; |
||||
|
|
||||
|
/**
|
||||
|
* getsockopt(2) wrapper. Equivalent to |
||||
|
* `getsockopt(this->Get(), level, opt_name, opt_val, opt_len)`. Code that uses this |
||||
|
* wrapper can be unit tested if this method is overridden by a mock Sock implementation. |
||||
|
*/ |
||||
|
virtual int GetSockOpt(int level, |
||||
|
int opt_name, |
||||
|
void* opt_val, |
||||
|
socklen_t* opt_len) const; |
||||
|
|
||||
|
using Event = uint8_t; |
||||
|
|
||||
|
/**
|
||||
|
* If passed to `Wait()`, then it will wait for readiness to read from the socket. |
||||
|
*/ |
||||
|
static constexpr Event RECV = 0b01; |
||||
|
|
||||
|
/**
|
||||
|
* If passed to `Wait()`, then it will wait for readiness to send to the socket. |
||||
|
*/ |
||||
|
static constexpr Event SEND = 0b10; |
||||
|
|
||||
|
/**
|
||||
|
* Wait for readiness for input (recv) or output (send). |
||||
|
* @param[in] timeout Wait this much for at least one of the requested events to occur. |
||||
|
* @param[in] requested Wait for those events, bitwise-or of `RECV` and `SEND`. |
||||
|
* @param[out] occurred If not nullptr and `true` is returned, then upon return this |
||||
|
* indicates which of the requested events occurred. A timeout is indicated by return |
||||
|
* value of `true` and `occurred` being set to 0. |
||||
|
* @return true on success and false otherwise |
||||
|
*/ |
||||
|
virtual bool Wait(std::chrono::milliseconds timeout, |
||||
|
Event requested, |
||||
|
Event* occurred = nullptr) const; |
||||
|
|
||||
|
/* Higher level, convenience, methods. These may throw. */ |
||||
|
|
||||
|
/**
|
||||
|
* Send the given data, retrying on transient errors. |
||||
|
* @param[in] data Data to send. |
||||
|
* @param[in] timeout Timeout for the entire operation. |
||||
|
* @param[in] interrupt If this is signaled then the operation is canceled. |
||||
|
* @throws std::runtime_error if the operation cannot be completed. In this case only some of |
||||
|
* the data will be written to the socket. |
||||
|
*/ |
||||
|
virtual void SendComplete(const std::string& data, |
||||
|
std::chrono::milliseconds timeout) const; |
||||
|
|
||||
|
/**
|
||||
|
* Read from socket until a terminator character is encountered. Will never consume bytes past |
||||
|
* the terminator from the socket. |
||||
|
* @param[in] terminator Character up to which to read from the socket. |
||||
|
* @param[in] timeout Timeout for the entire operation. |
||||
|
* @param[in] interrupt If this is signaled then the operation is canceled. |
||||
|
* @param[in] max_data The maximum amount of data (in bytes) to receive. If this many bytes |
||||
|
* are received and there is still no terminator, then this method will throw an exception. |
||||
|
* @return The data that has been read, without the terminating character. |
||||
|
* @throws std::runtime_error if the operation cannot be completed. In this case some bytes may |
||||
|
* have been consumed from the socket. |
||||
|
*/ |
||||
|
virtual std::string RecvUntilTerminator(uint8_t terminator, |
||||
|
std::chrono::milliseconds timeout, |
||||
|
size_t max_data) const; |
||||
|
|
||||
|
/**
|
||||
|
* Check if still connected. |
||||
|
* @param[out] errmsg The error string, if the socket has been disconnected. |
||||
|
* @return true if connected |
||||
|
*/ |
||||
|
virtual bool IsConnected(std::string& errmsg) const; |
||||
|
|
||||
|
protected: |
||||
|
/**
|
||||
|
* Contained socket. `INVALID_SOCKET` designates the object is empty. |
||||
|
*/ |
||||
|
SOCKET m_socket; |
||||
|
}; |
||||
|
|
||||
|
/** Return readable error string for a network error code */ |
||||
|
std::string NetworkErrorString(int err); |
||||
|
|
||||
|
/** Close socket and set hSocket to INVALID_SOCKET */ |
||||
|
bool CloseSocket(SOCKET& hSocket); |
||||
|
|
||||
|
#endif // BITCOIN_UTIL_SOCK_H
|
@ -0,0 +1,68 @@ |
|||||
|
// Copyright (c) 2018-2019 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#include <util/spanparsing.h> |
||||
|
|
||||
|
#include <span.h> |
||||
|
|
||||
|
#include <string> |
||||
|
#include <vector> |
||||
|
|
||||
|
namespace spanparsing { |
||||
|
|
||||
|
bool Const(const std::string& str, Span<const char>& sp) |
||||
|
{ |
||||
|
if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) { |
||||
|
sp = sp.subspan(str.size()); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool Func(const std::string& str, Span<const char>& sp) |
||||
|
{ |
||||
|
if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) { |
||||
|
sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
Span<const char> Expr(Span<const char>& sp) |
||||
|
{ |
||||
|
int level = 0; |
||||
|
auto it = sp.begin(); |
||||
|
while (it != sp.end()) { |
||||
|
if (*it == '(' || *it == '{') { |
||||
|
++level; |
||||
|
} else if (level && (*it == ')' || *it == '}')) { |
||||
|
--level; |
||||
|
} else if (level == 0 && (*it == ')' || *it == '}' || *it == ',')) { |
||||
|
break; |
||||
|
} |
||||
|
++it; |
||||
|
} |
||||
|
Span<const char> ret = sp.first(it - sp.begin()); |
||||
|
sp = sp.subspan(it - sp.begin()); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
std::vector<Span<const char>> Split(const Span<const char>& sp, char sep) |
||||
|
{ |
||||
|
std::vector<Span<const char>> ret; |
||||
|
auto it = sp.begin(); |
||||
|
auto start = it; |
||||
|
while (it != sp.end()) { |
||||
|
if (*it == sep) { |
||||
|
ret.emplace_back(start, it); |
||||
|
start = it + 1; |
||||
|
} |
||||
|
++it; |
||||
|
} |
||||
|
ret.emplace_back(start, it); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
} // namespace spanparsing
|
@ -0,0 +1,50 @@ |
|||||
|
// Copyright (c) 2018-2019 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef BITCOIN_UTIL_SPANPARSING_H |
||||
|
#define BITCOIN_UTIL_SPANPARSING_H |
||||
|
|
||||
|
#include <span.h> |
||||
|
#include <string> |
||||
|
#include <vector> |
||||
|
|
||||
|
namespace spanparsing { |
||||
|
|
||||
|
/** Parse a constant.
|
||||
|
* |
||||
|
* If sp's initial part matches str, sp is updated to skip that part, and true is returned. |
||||
|
* Otherwise sp is unmodified and false is returned. |
||||
|
*/ |
||||
|
bool Const(const std::string& str, Span<const char>& sp); |
||||
|
|
||||
|
/** Parse a function call.
|
||||
|
* |
||||
|
* If sp's initial part matches str + "(", and sp ends with ")", sp is updated to be the |
||||
|
* section between the braces, and true is returned. Otherwise sp is unmodified and false |
||||
|
* is returned. |
||||
|
*/ |
||||
|
bool Func(const std::string& str, Span<const char>& sp); |
||||
|
|
||||
|
/** Extract the expression that sp begins with.
|
||||
|
* |
||||
|
* This function will return the initial part of sp, up to (but not including) the first |
||||
|
* comma or closing brace, skipping ones that are surrounded by braces. So for example, |
||||
|
* for "foo(bar(1),2),3" the initial part "foo(bar(1),2)" will be returned. sp will be |
||||
|
* updated to skip the initial part that is returned. |
||||
|
*/ |
||||
|
Span<const char> Expr(Span<const char>& sp); |
||||
|
|
||||
|
/** Split a string on every instance of sep, returning a vector.
|
||||
|
* |
||||
|
* If sep does not occur in sp, a singleton with the entirety of sp is returned. |
||||
|
* |
||||
|
* Note that this function does not care about braces, so splitting |
||||
|
* "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. |
||||
|
*/ |
||||
|
std::vector<Span<const char>> Split(const Span<const char>& sp, char sep); |
||||
|
|
||||
|
} // namespace spanparsing
|
||||
|
|
||||
|
#endif // BITCOIN_UTIL_SPANPARSING_H
|
@ -0,0 +1,655 @@ |
|||||
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
|
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#include "util/strencodings.h" |
||||
|
#include "util/string.h" |
||||
|
|
||||
|
#include <tinyformat.h> |
||||
|
|
||||
|
#include <algorithm> |
||||
|
#include <cstdlib> |
||||
|
#include <cstring> |
||||
|
#include <iomanip> |
||||
|
#include <errno.h> |
||||
|
#include <limits> |
||||
|
|
||||
|
using namespace std; |
||||
|
|
||||
|
static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; |
||||
|
|
||||
|
static const std::string SAFE_CHARS[] = |
||||
|
{ |
||||
|
CHARS_ALPHA_NUM + " .,;-_/:?@()", // SAFE_CHARS_DEFAULT
|
||||
|
CHARS_ALPHA_NUM + " .,;-_?@", // SAFE_CHARS_UA_COMMENT
|
||||
|
CHARS_ALPHA_NUM + ".-_", // SAFE_CHARS_FILENAME
|
||||
|
CHARS_ALPHA_NUM + "!*'();:@&=+$,/?#[]-_.~%", // SAFE_CHARS_URI
|
||||
|
}; |
||||
|
|
||||
|
std::string SanitizeString(const std::string& str, int rule) |
||||
|
{ |
||||
|
std::string strResult; |
||||
|
for (std::string::size_type i = 0; i < str.size(); i++) |
||||
|
{ |
||||
|
if (SAFE_CHARS[rule].find(str[i]) != std::string::npos) |
||||
|
strResult.push_back(str[i]); |
||||
|
} |
||||
|
return strResult; |
||||
|
} |
||||
|
|
||||
|
string SanitizeFilename(const string& str) |
||||
|
{ |
||||
|
/**
|
||||
|
* safeChars chosen to restrict filename, keeping it simple to avoid cross-platform issues. |
||||
|
* http://stackoverflow.com/a/2306003
|
||||
|
*/ |
||||
|
static string safeChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"); |
||||
|
string strResult; |
||||
|
for (std::string::size_type i = 0; i < str.size(); i++) |
||||
|
{ |
||||
|
if (safeChars.find(str[i]) != std::string::npos) |
||||
|
strResult.push_back(str[i]); |
||||
|
} |
||||
|
return strResult; |
||||
|
} |
||||
|
|
||||
|
std::string HexInt(uint32_t val) |
||||
|
{ |
||||
|
std::stringstream ss; |
||||
|
ss << std::setfill('0') << std::setw(sizeof(uint32_t) * 2) << std::hex << val; |
||||
|
return ss.str(); |
||||
|
} |
||||
|
|
||||
|
uint32_t ParseHexToUInt32(const std::string& str) { |
||||
|
std::istringstream converter(str); |
||||
|
uint32_t value; |
||||
|
converter >> std::hex >> value; |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
const signed char p_util_hexdigit[256] = |
||||
|
{ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1, |
||||
|
-1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, |
||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, }; |
||||
|
|
||||
|
signed char HexDigit(char c) |
||||
|
{ |
||||
|
return p_util_hexdigit[(unsigned char)c]; |
||||
|
} |
||||
|
|
||||
|
bool IsHex(const std::string& str) |
||||
|
{ |
||||
|
for(std::string::const_iterator it(str.begin()); it != str.end(); ++it) |
||||
|
{ |
||||
|
if (HexDigit(*it) < 0) |
||||
|
return false; |
||||
|
} |
||||
|
return (str.size() > 0) && (str.size()%2 == 0); |
||||
|
} |
||||
|
|
||||
|
bool IsHexNumber(const std::string& str) |
||||
|
{ |
||||
|
size_t starting_location = 0; |
||||
|
if (str.size() > 2 && *str.begin() == '0' && *(str.begin()+1) == 'x') { |
||||
|
starting_location = 2; |
||||
|
} |
||||
|
for (const char c : str.substr(starting_location)) { |
||||
|
if (HexDigit(c) < 0) return false; |
||||
|
} |
||||
|
// Return false for empty string or "0x".
|
||||
|
return (str.size() > starting_location); |
||||
|
} |
||||
|
|
||||
|
std::vector<unsigned char> ParseHex(const char* psz) |
||||
|
{ |
||||
|
// convert hex dump to vector
|
||||
|
std::vector<unsigned char> vch; |
||||
|
while (true) |
||||
|
{ |
||||
|
while (IsSpace(*psz)) |
||||
|
psz++; |
||||
|
signed char c = HexDigit(*psz++); |
||||
|
if (c == (signed char)-1) |
||||
|
break; |
||||
|
unsigned char n = (c << 4); |
||||
|
c = HexDigit(*psz++); |
||||
|
if (c == (signed char)-1) |
||||
|
break; |
||||
|
n |= c; |
||||
|
vch.push_back(n); |
||||
|
} |
||||
|
return vch; |
||||
|
} |
||||
|
|
||||
|
std::vector<unsigned char> ParseHex(const std::string& str) |
||||
|
{ |
||||
|
return ParseHex(str.c_str()); |
||||
|
} |
||||
|
|
||||
|
void SplitHostPort(std::string in, int &portOut, std::string &hostOut) { |
||||
|
size_t colon = in.find_last_of(':'); |
||||
|
// if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator
|
||||
|
bool fHaveColon = colon != in.npos; |
||||
|
bool fBracketed = fHaveColon && (in[0]=='[' && in[colon-1]==']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe
|
||||
|
bool fMultiColon = fHaveColon && (in.find_last_of(':',colon-1) != in.npos); |
||||
|
if (fHaveColon && (colon==0 || fBracketed || !fMultiColon)) { |
||||
|
int32_t n; |
||||
|
if (ParseInt32(in.substr(colon + 1), &n) && n > 0 && n < 0x10000) { |
||||
|
in = in.substr(0, colon); |
||||
|
portOut = n; |
||||
|
} |
||||
|
} |
||||
|
if (in.size()>0 && in[0] == '[' && in[in.size()-1] == ']') |
||||
|
hostOut = in.substr(1, in.size()-2); |
||||
|
else |
||||
|
hostOut = in; |
||||
|
} |
||||
|
|
||||
|
std::string EncodeBase64(Span<const unsigned char> input) |
||||
|
{ |
||||
|
static const char *pbase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
||||
|
|
||||
|
std::string str; |
||||
|
str.reserve(((input.size() + 2) / 3) * 4); |
||||
|
ConvertBits<8, 6, true>([&](int v) { str += pbase64[v]; }, input.begin(), input.end()); |
||||
|
while (str.size() % 4) str += '='; |
||||
|
return str; |
||||
|
} |
||||
|
|
||||
|
std::string EncodeBase64(const unsigned char* pch, size_t len) |
||||
|
{ |
||||
|
static const char *pbase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
||||
|
|
||||
|
std::string str; |
||||
|
str.reserve(((len + 2) / 3) * 4); |
||||
|
ConvertBits<8, 6, true>([&](int v) { str += pbase64[v]; }, pch, pch + len); |
||||
|
while (str.size() % 4) str += '='; |
||||
|
return str; |
||||
|
} |
||||
|
|
||||
|
std::string EncodeBase64(const std::string& str) |
||||
|
{ |
||||
|
return EncodeBase64((const unsigned char*)str.data(), str.size()); |
||||
|
} |
||||
|
|
||||
|
std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) |
||||
|
{ |
||||
|
static const int decode64_table[256] = |
||||
|
{ |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, |
||||
|
-1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, |
||||
|
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, |
||||
|
49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 |
||||
|
}; |
||||
|
|
||||
|
const char* e = p; |
||||
|
std::vector<uint8_t> val; |
||||
|
val.reserve(strlen(p)); |
||||
|
while (*p != 0) { |
||||
|
int x = decode64_table[(unsigned char)*p]; |
||||
|
if (x == -1) break; |
||||
|
val.push_back(x); |
||||
|
++p; |
||||
|
} |
||||
|
|
||||
|
std::vector<unsigned char> ret; |
||||
|
ret.reserve((val.size() * 3) / 4); |
||||
|
bool valid = ConvertBits<6, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); |
||||
|
|
||||
|
const char* q = p; |
||||
|
while (valid && *p != 0) { |
||||
|
if (*p != '=') { |
||||
|
valid = false; |
||||
|
break; |
||||
|
} |
||||
|
++p; |
||||
|
} |
||||
|
valid = valid && (p - e) % 4 == 0 && p - q < 4; |
||||
|
if (pf_invalid) *pf_invalid = !valid; |
||||
|
|
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
std::string DecodeBase64(const std::string& str, bool* pf_invalid) |
||||
|
{ |
||||
|
if (!ValidAsCString(str)) { |
||||
|
if (pf_invalid) { |
||||
|
*pf_invalid = true; |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
std::vector<unsigned char> vchRet = DecodeBase64(str.c_str(), pf_invalid); |
||||
|
return std::string((const char*)vchRet.data(), vchRet.size()); |
||||
|
} |
||||
|
|
||||
|
std::string EncodeBase32(Span<const unsigned char> input, bool pad) |
||||
|
{ |
||||
|
static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; |
||||
|
|
||||
|
std::string str; |
||||
|
str.reserve(((input.size() + 4) / 5) * 8); |
||||
|
ConvertBits<8, 5, true>([&](int v) { str += pbase32[v]; }, input.begin(), input.end()); |
||||
|
if (pad) { |
||||
|
while (str.size() % 8) { |
||||
|
str += '='; |
||||
|
} |
||||
|
} |
||||
|
return str; |
||||
|
} |
||||
|
|
||||
|
std::string EncodeBase32(const std::string& str, bool pad) |
||||
|
{ |
||||
|
return EncodeBase32(MakeUCharSpan(str), pad); |
||||
|
} |
||||
|
|
||||
|
std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) |
||||
|
{ |
||||
|
static const int decode32_table[256] = |
||||
|
{ |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 0, 1, 2, |
||||
|
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, |
||||
|
23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 |
||||
|
}; |
||||
|
|
||||
|
const char* e = p; |
||||
|
std::vector<uint8_t> val; |
||||
|
val.reserve(strlen(p)); |
||||
|
while (*p != 0) { |
||||
|
int x = decode32_table[(unsigned char)*p]; |
||||
|
if (x == -1) break; |
||||
|
val.push_back(x); |
||||
|
++p; |
||||
|
} |
||||
|
|
||||
|
std::vector<unsigned char> ret; |
||||
|
ret.reserve((val.size() * 5) / 8); |
||||
|
bool valid = ConvertBits<5, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); |
||||
|
|
||||
|
const char* q = p; |
||||
|
while (valid && *p != 0) { |
||||
|
if (*p != '=') { |
||||
|
valid = false; |
||||
|
break; |
||||
|
} |
||||
|
++p; |
||||
|
} |
||||
|
valid = valid && (p - e) % 8 == 0 && p - q < 8; |
||||
|
if (pf_invalid) *pf_invalid = !valid; |
||||
|
|
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
std::string DecodeBase32(const std::string& str, bool* pf_invalid) |
||||
|
{ |
||||
|
if (!ValidAsCString(str)) { |
||||
|
if (pf_invalid) { |
||||
|
*pf_invalid = true; |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
std::vector<unsigned char> vchRet = DecodeBase32(str.c_str(), pf_invalid); |
||||
|
return std::string((const char*)vchRet.data(), vchRet.size()); |
||||
|
} |
||||
|
|
||||
|
static bool ParsePrechecks(const std::string& str) |
||||
|
{ |
||||
|
if (str.empty()) // No empty string allowed
|
||||
|
return false; |
||||
|
if (str.size() >= 1 && (IsSpace(str[0]) || IsSpace(str[str.size()-1]))) // No padding allowed
|
||||
|
return false; |
||||
|
if (!ValidAsCString(str)) // No embedded NUL characters allowed
|
||||
|
return false; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool ParseInt32(const std::string& str, int32_t *out) |
||||
|
{ |
||||
|
if (!ParsePrechecks(str)) |
||||
|
return false; |
||||
|
char *endp = nullptr; |
||||
|
errno = 0; // strtol will not set errno if valid
|
||||
|
long int n = strtol(str.c_str(), &endp, 10); |
||||
|
if(out) *out = (int32_t)n; |
||||
|
// Note that strtol returns a *long int*, so even if strtol doesn't report an over/underflow
|
||||
|
// we still have to check that the returned value is within the range of an *int32_t*. On 64-bit
|
||||
|
// platforms the size of these types may be different.
|
||||
|
return endp && *endp == 0 && !errno && |
||||
|
n >= std::numeric_limits<int32_t>::min() && |
||||
|
n <= std::numeric_limits<int32_t>::max(); |
||||
|
} |
||||
|
|
||||
|
bool ParseInt64(const std::string& str, int64_t *out) |
||||
|
{ |
||||
|
if (!ParsePrechecks(str)) |
||||
|
return false; |
||||
|
char *endp = nullptr; |
||||
|
errno = 0; // strtoll will not set errno if valid
|
||||
|
long long int n = strtoll(str.c_str(), &endp, 10); |
||||
|
if(out) *out = (int64_t)n; |
||||
|
// Note that strtoll returns a *long long int*, so even if strtol doesn't report an over/underflow
|
||||
|
// we still have to check that the returned value is within the range of an *int64_t*.
|
||||
|
return endp && *endp == 0 && !errno && |
||||
|
n >= std::numeric_limits<int64_t>::min() && |
||||
|
n <= std::numeric_limits<int64_t>::max(); |
||||
|
} |
||||
|
|
||||
|
bool ParseUInt8(const std::string& str, uint8_t *out) |
||||
|
{ |
||||
|
uint32_t u32; |
||||
|
if (!ParseUInt32(str, &u32) || u32 > std::numeric_limits<uint8_t>::max()) { |
||||
|
return false; |
||||
|
} |
||||
|
if (out != nullptr) { |
||||
|
*out = static_cast<uint8_t>(u32); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool ParseUInt32(const std::string& str, uint32_t *out) |
||||
|
{ |
||||
|
if (!ParsePrechecks(str)) |
||||
|
return false; |
||||
|
if (str.size() >= 1 && str[0] == '-') // Reject negative values, unfortunately strtoul accepts these by default if they fit in the range
|
||||
|
return false; |
||||
|
char *endp = nullptr; |
||||
|
errno = 0; // strtoul will not set errno if valid
|
||||
|
unsigned long int n = strtoul(str.c_str(), &endp, 10); |
||||
|
if(out) *out = (uint32_t)n; |
||||
|
// Note that strtoul returns a *unsigned long int*, so even if it doesn't report an over/underflow
|
||||
|
// we still have to check that the returned value is within the range of an *uint32_t*. On 64-bit
|
||||
|
// platforms the size of these types may be different.
|
||||
|
return endp && *endp == 0 && !errno && |
||||
|
n <= std::numeric_limits<uint32_t>::max(); |
||||
|
} |
||||
|
|
||||
|
bool ParseUInt64(const std::string& str, uint64_t *out) |
||||
|
{ |
||||
|
if (!ParsePrechecks(str)) |
||||
|
return false; |
||||
|
if (str.size() >= 1 && str[0] == '-') // Reject negative values, unfortunately strtoull accepts these by default if they fit in the range
|
||||
|
return false; |
||||
|
char *endp = nullptr; |
||||
|
errno = 0; // strtoull will not set errno if valid
|
||||
|
unsigned long long int n = strtoull(str.c_str(), &endp, 10); |
||||
|
if(out) *out = (uint64_t)n; |
||||
|
// Note that strtoull returns a *unsigned long long int*, so even if it doesn't report an over/underflow
|
||||
|
// we still have to check that the returned value is within the range of an *uint64_t*.
|
||||
|
return endp && *endp == 0 && !errno && |
||||
|
n <= std::numeric_limits<uint64_t>::max(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
bool ParseDouble(const std::string& str, double *out) |
||||
|
{ |
||||
|
if (!ParsePrechecks(str)) |
||||
|
return false; |
||||
|
if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') // No hexadecimal floats allowed
|
||||
|
return false; |
||||
|
std::istringstream text(str); |
||||
|
text.imbue(std::locale::classic()); |
||||
|
double result; |
||||
|
text >> result; |
||||
|
if(out) *out = result; |
||||
|
return text.eof() && !text.fail(); |
||||
|
} |
||||
|
|
||||
|
std::string FormatParagraph(const std::string& in, size_t width, size_t indent) |
||||
|
{ |
||||
|
std::stringstream out; |
||||
|
size_t ptr = 0; |
||||
|
size_t indented = 0; |
||||
|
while (ptr < in.size()) |
||||
|
{ |
||||
|
size_t lineend = in.find_first_of('\n', ptr); |
||||
|
if (lineend == std::string::npos) { |
||||
|
lineend = in.size(); |
||||
|
} |
||||
|
const size_t linelen = lineend - ptr; |
||||
|
const size_t rem_width = width - indented; |
||||
|
if (linelen <= rem_width) { |
||||
|
out << in.substr(ptr, linelen + 1); |
||||
|
ptr = lineend + 1; |
||||
|
indented = 0; |
||||
|
} else { |
||||
|
size_t finalspace = in.find_last_of(" \n", ptr + rem_width); |
||||
|
if (finalspace == std::string::npos || finalspace < ptr) { |
||||
|
// No place to break; just include the entire word and move on
|
||||
|
finalspace = in.find_first_of("\n ", ptr); |
||||
|
if (finalspace == std::string::npos) { |
||||
|
// End of the string, just add it and break
|
||||
|
out << in.substr(ptr); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
out << in.substr(ptr, finalspace - ptr) << "\n"; |
||||
|
if (in[finalspace] == '\n') { |
||||
|
indented = 0; |
||||
|
} else if (indent) { |
||||
|
out << std::string(indent, ' '); |
||||
|
indented = indent; |
||||
|
} |
||||
|
ptr = finalspace + 1; |
||||
|
} |
||||
|
} |
||||
|
return out.str(); |
||||
|
} |
||||
|
|
||||
|
std::string i64tostr(int64_t n) |
||||
|
{ |
||||
|
return strprintf("%d", n); |
||||
|
} |
||||
|
|
||||
|
std::string itostr(int n) |
||||
|
{ |
||||
|
return strprintf("%d", n); |
||||
|
} |
||||
|
|
||||
|
int64_t atoi64(const char* psz) |
||||
|
{ |
||||
|
#ifdef _MSC_VER |
||||
|
return _atoi64(psz); |
||||
|
#else |
||||
|
return strtoll(psz, nullptr, 10); |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
int64_t atoi64(const std::string& str) |
||||
|
{ |
||||
|
#ifdef _MSC_VER |
||||
|
return _atoi64(str.c_str()); |
||||
|
#else |
||||
|
return strtoll(str.c_str(), nullptr, 10); |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
int atoi(const std::string& str) |
||||
|
{ |
||||
|
return atoi(str.c_str()); |
||||
|
} |
||||
|
|
||||
|
/** Upper bound for mantissa.
|
||||
|
* 10^18-1 is the largest arbitrary decimal that will fit in a signed 64-bit integer. |
||||
|
* Larger integers cannot consist of arbitrary combinations of 0-9: |
||||
|
* |
||||
|
* 999999999999999999 1^18-1 |
||||
|
* 9223372036854775807 (1<<63)-1 (max int64_t) |
||||
|
* 9999999999999999999 1^19-1 (would overflow) |
||||
|
*/ |
||||
|
static const int64_t UPPER_BOUND = 1000000000000000000LL - 1LL; |
||||
|
|
||||
|
/** Helper function for ParseFixedPoint */ |
||||
|
static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantissa_tzeros) |
||||
|
{ |
||||
|
if(ch == '0') |
||||
|
++mantissa_tzeros; |
||||
|
else { |
||||
|
for (int i=0; i<=mantissa_tzeros; ++i) { |
||||
|
if (mantissa > (UPPER_BOUND / 10LL)) |
||||
|
return false; /* overflow */ |
||||
|
mantissa *= 10; |
||||
|
} |
||||
|
mantissa += ch - '0'; |
||||
|
mantissa_tzeros = 0; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) |
||||
|
{ |
||||
|
int64_t mantissa = 0; |
||||
|
int64_t exponent = 0; |
||||
|
int mantissa_tzeros = 0; |
||||
|
bool mantissa_sign = false; |
||||
|
bool exponent_sign = false; |
||||
|
int ptr = 0; |
||||
|
int end = val.size(); |
||||
|
int point_ofs = 0; |
||||
|
|
||||
|
if (ptr < end && val[ptr] == '-') { |
||||
|
mantissa_sign = true; |
||||
|
++ptr; |
||||
|
} |
||||
|
if (ptr < end) |
||||
|
{ |
||||
|
if (val[ptr] == '0') { |
||||
|
/* pass single 0 */ |
||||
|
++ptr; |
||||
|
} else if (val[ptr] >= '1' && val[ptr] <= '9') { |
||||
|
while (ptr < end && IsDigit(val[ptr])) { |
||||
|
if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros)) |
||||
|
return false; /* overflow */ |
||||
|
++ptr; |
||||
|
} |
||||
|
} else return false; /* missing expected digit */ |
||||
|
} else return false; /* empty string or loose '-' */ |
||||
|
if (ptr < end && val[ptr] == '.') |
||||
|
{ |
||||
|
++ptr; |
||||
|
if (ptr < end && IsDigit(val[ptr])) |
||||
|
{ |
||||
|
while (ptr < end && IsDigit(val[ptr])) { |
||||
|
if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros)) |
||||
|
return false; /* overflow */ |
||||
|
++ptr; |
||||
|
++point_ofs; |
||||
|
} |
||||
|
} else return false; /* missing expected digit */ |
||||
|
} |
||||
|
if (ptr < end && (val[ptr] == 'e' || val[ptr] == 'E')) |
||||
|
{ |
||||
|
++ptr; |
||||
|
if (ptr < end && val[ptr] == '+') |
||||
|
++ptr; |
||||
|
else if (ptr < end && val[ptr] == '-') { |
||||
|
exponent_sign = true; |
||||
|
++ptr; |
||||
|
} |
||||
|
if (ptr < end && IsDigit(val[ptr])) { |
||||
|
while (ptr < end && IsDigit(val[ptr])) { |
||||
|
if (exponent > (UPPER_BOUND / 10LL)) |
||||
|
return false; /* overflow */ |
||||
|
exponent = exponent * 10 + val[ptr] - '0'; |
||||
|
++ptr; |
||||
|
} |
||||
|
} else return false; /* missing expected digit */ |
||||
|
} |
||||
|
if (ptr != end) |
||||
|
return false; /* trailing garbage */ |
||||
|
|
||||
|
/* finalize exponent */ |
||||
|
if (exponent_sign) |
||||
|
exponent = -exponent; |
||||
|
exponent = exponent - point_ofs + mantissa_tzeros; |
||||
|
|
||||
|
/* finalize mantissa */ |
||||
|
if (mantissa_sign) |
||||
|
mantissa = -mantissa; |
||||
|
|
||||
|
/* convert to one 64-bit fixed-point value */ |
||||
|
exponent += decimals; |
||||
|
if (exponent < 0) |
||||
|
return false; /* cannot represent values smaller than 10^-decimals */ |
||||
|
if (exponent >= 18) |
||||
|
return false; /* cannot represent values larger than or equal to 10^(18-decimals) */ |
||||
|
|
||||
|
for (int i=0; i < exponent; ++i) { |
||||
|
if (mantissa > (UPPER_BOUND / 10LL) || mantissa < -(UPPER_BOUND / 10LL)) |
||||
|
return false; /* overflow */ |
||||
|
mantissa *= 10; |
||||
|
} |
||||
|
if (mantissa > UPPER_BOUND || mantissa < -UPPER_BOUND) |
||||
|
return false; /* overflow */ |
||||
|
|
||||
|
if (amount_out) |
||||
|
*amount_out = mantissa; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
std::string ToLower(const std::string& str) |
||||
|
{ |
||||
|
std::string r; |
||||
|
for (auto ch : str) r += ToLower((unsigned char)ch); |
||||
|
return r; |
||||
|
} |
||||
|
|
||||
|
std::string ToUpper(const std::string& str) |
||||
|
{ |
||||
|
std::string r; |
||||
|
for (auto ch : str) r += ToUpper((unsigned char)ch); |
||||
|
return r; |
||||
|
} |
||||
|
|
||||
|
std::string Capitalize(std::string str) |
||||
|
{ |
||||
|
if (str.empty()) return str; |
||||
|
str[0] = ToUpper(str.front()); |
||||
|
return str; |
||||
|
} |
||||
|
|
||||
|
std::string HexStr(const Span<const uint8_t> s) |
||||
|
{ |
||||
|
std::string rv; |
||||
|
static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', |
||||
|
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; |
||||
|
rv.reserve(s.size() * 2); |
||||
|
for (uint8_t v: s) { |
||||
|
rv.push_back(hexmap[v >> 4]); |
||||
|
rv.push_back(hexmap[v & 15]); |
||||
|
} |
||||
|
return rv; |
||||
|
} |
@ -0,0 +1,302 @@ |
|||||
|
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
|
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
/**
|
||||
|
* Utilities for converting data from/to strings. |
||||
|
*/ |
||||
|
#ifndef BITCOIN_UTIL_STRENCODINGS_H |
||||
|
#define BITCOIN_UTIL_STRENCODINGS_H |
||||
|
|
||||
|
#include "attributes.h" |
||||
|
#include "span.h" |
||||
|
|
||||
|
#include <cstdint> |
||||
|
#include <iterator> |
||||
|
#include <string> |
||||
|
#include <vector> |
||||
|
|
||||
|
#define BEGIN(a) ((char*)&(a)) |
||||
|
#define END(a) ((char*)&((&(a))[1])) |
||||
|
#define UBEGIN(a) ((unsigned char*)&(a)) |
||||
|
#define UEND(a) ((unsigned char*)&((&(a))[1])) |
||||
|
#define ARRAYLEN(array) (sizeof(array)/sizeof((array)[0])) |
||||
|
|
||||
|
/** This is needed because the foreach macro can't get over the comma in pair<t1, t2> */ |
||||
|
#define PAIRTYPE(t1, t2) std::pair<t1, t2> |
||||
|
|
||||
|
/** Used by SanitizeString() */ |
||||
|
enum SafeChars |
||||
|
{ |
||||
|
SAFE_CHARS_DEFAULT, //!< The full set of allowed chars
|
||||
|
SAFE_CHARS_UA_COMMENT, //!< BIP-0014 subset
|
||||
|
SAFE_CHARS_FILENAME, //!< Chars allowed in filenames
|
||||
|
SAFE_CHARS_URI, //!< Chars allowed in URIs (RFC 3986)
|
||||
|
}; |
||||
|
|
||||
|
std::string SanitizeFilename(const std::string& str); |
||||
|
/**
|
||||
|
* Remove unsafe chars. Safe chars chosen to allow simple messages/URLs/email |
||||
|
* addresses, but avoid anything even possibly remotely dangerous like & or > |
||||
|
* @param[in] str The string to sanitize |
||||
|
* @param[in] rule The set of safe chars to choose (default: least restrictive) |
||||
|
* @return A new string without unsafe chars |
||||
|
*/ |
||||
|
std::string SanitizeString(const std::string& str, int rule = SAFE_CHARS_DEFAULT); |
||||
|
std::string HexInt(uint32_t val); |
||||
|
uint32_t ParseHexToUInt32(const std::string& str); |
||||
|
std::vector<unsigned char> ParseHex(const char* psz); |
||||
|
std::vector<unsigned char> ParseHex(const std::string& str); |
||||
|
signed char HexDigit(char c); |
||||
|
/* Returns true if each character in str is a hex character, and has an even
|
||||
|
* number of hex digits.*/ |
||||
|
bool IsHex(const std::string& str); |
||||
|
/**
|
||||
|
* Return true if the string is a hex number, optionally prefixed with "0x" |
||||
|
*/ |
||||
|
bool IsHexNumber(const std::string& str); |
||||
|
std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid = nullptr); |
||||
|
std::string DecodeBase64(const std::string& str, bool* pf_invalid = nullptr); |
||||
|
std::string EncodeBase64(Span<const unsigned char> input); |
||||
|
std::string EncodeBase64(const unsigned char* pch, size_t len); |
||||
|
std::string EncodeBase64(const std::string& str); |
||||
|
std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid = nullptr); |
||||
|
std::string DecodeBase32(const std::string& str, bool* pf_invalid = nullptr); |
||||
|
|
||||
|
/**
|
||||
|
* Base32 encode. |
||||
|
* If `pad` is true, then the output will be padded with '=' so that its length |
||||
|
* is a multiple of 8. |
||||
|
*/ |
||||
|
std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); |
||||
|
|
||||
|
/**
|
||||
|
* Base32 encode. |
||||
|
* If `pad` is true, then the output will be padded with '=' so that its length |
||||
|
* is a multiple of 8. |
||||
|
*/ |
||||
|
std::string EncodeBase32(const std::string& str, bool pad = true); |
||||
|
|
||||
|
void SplitHostPort(std::string in, int& portOut, std::string& hostOut); |
||||
|
std::string i64tostr(int64_t n); |
||||
|
std::string itostr(int n); |
||||
|
int64_t atoi64(const char* psz); |
||||
|
int64_t atoi64(const std::string& str); |
||||
|
int atoi(const std::string& str); |
||||
|
|
||||
|
/**
|
||||
|
* Tests if the given character is a decimal digit. |
||||
|
* @param[in] c character to test |
||||
|
* @return true if the argument is a decimal digit; otherwise false. |
||||
|
*/ |
||||
|
constexpr bool IsDigit(char c) |
||||
|
{ |
||||
|
return c >= '0' && c <= '9'; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Tests if the given character is a whitespace character. The whitespace characters |
||||
|
* are: space, form-feed ('\f'), newline ('\n'), carriage return ('\r'), horizontal |
||||
|
* tab ('\t'), and vertical tab ('\v'). |
||||
|
* |
||||
|
* This function is locale independent. Under the C locale this function gives the |
||||
|
* same result as std::isspace. |
||||
|
* |
||||
|
* @param[in] c character to test |
||||
|
* @return true if the argument is a whitespace character; otherwise false |
||||
|
*/ |
||||
|
constexpr inline bool IsSpace(char c) noexcept { |
||||
|
return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Convert string to signed 32-bit integer with strict parse error feedback. |
||||
|
* @returns true if the entire string could be parsed as valid integer, |
||||
|
* false if not the entire string could be parsed or when overflow or underflow occurred. |
||||
|
*/ |
||||
|
bool ParseInt32(const std::string& str, int32_t *out); |
||||
|
|
||||
|
/**
|
||||
|
* Convert string to signed 64-bit integer with strict parse error feedback. |
||||
|
* @returns true if the entire string could be parsed as valid integer, |
||||
|
* false if not the entire string could be parsed or when overflow or underflow occurred. |
||||
|
*/ |
||||
|
bool ParseInt64(const std::string& str, int64_t *out); |
||||
|
|
||||
|
/**
|
||||
|
* Convert decimal string to unsigned 8-bit integer with strict parse error feedback. |
||||
|
* @returns true if the entire string could be parsed as valid integer, |
||||
|
* false if not the entire string could be parsed or when overflow or underflow occurred. |
||||
|
*/ |
||||
|
bool ParseUInt8(const std::string& str, uint8_t *out); |
||||
|
|
||||
|
/**
|
||||
|
* Convert decimal string to unsigned 32-bit integer with strict parse error feedback. |
||||
|
* @returns true if the entire string could be parsed as valid integer, |
||||
|
* false if not the entire string could be parsed or when overflow or underflow occurred. |
||||
|
*/ |
||||
|
bool ParseUInt32(const std::string& str, uint32_t *out); |
||||
|
|
||||
|
/**
|
||||
|
* Convert decimal string to unsigned 64-bit integer with strict parse error feedback. |
||||
|
* @returns true if the entire string could be parsed as valid integer, |
||||
|
* false if not the entire string could be parsed or when overflow or underflow occurred. |
||||
|
*/ |
||||
|
bool ParseUInt64(const std::string& str, uint64_t *out); |
||||
|
|
||||
|
/**
|
||||
|
* Convert string to double with strict parse error feedback. |
||||
|
* @returns true if the entire string could be parsed as valid double, |
||||
|
* false if not the entire string could be parsed or when overflow or underflow occurred. |
||||
|
*/ |
||||
|
bool ParseDouble(const std::string& str, double *out); |
||||
|
|
||||
|
template<typename T> |
||||
|
std::string HexStr(const T itbegin, const T itend, bool fSpaces=false) |
||||
|
{ |
||||
|
std::string rv; |
||||
|
static const char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', |
||||
|
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; |
||||
|
rv.reserve((itend-itbegin)*3); |
||||
|
for(T it = itbegin; it < itend; ++it) |
||||
|
{ |
||||
|
unsigned char val = (unsigned char)(*it); |
||||
|
if(fSpaces && it != itbegin) |
||||
|
rv.push_back(' '); |
||||
|
rv.push_back(hexmap[val>>4]); |
||||
|
rv.push_back(hexmap[val&15]); |
||||
|
} |
||||
|
|
||||
|
return rv; |
||||
|
} |
||||
|
|
||||
|
template<typename T> |
||||
|
inline std::string HexStr(const T& vch, bool fSpaces=false) |
||||
|
{ |
||||
|
return HexStr(vch.begin(), vch.end(), fSpaces); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Convert a span of bytes to a lower-case hexadecimal string. |
||||
|
*/ |
||||
|
std::string HexStr(const Span<const uint8_t> s); |
||||
|
inline std::string HexStr(const Span<const char> s) { return HexStr(MakeUCharSpan(s)); } |
||||
|
|
||||
|
/**
|
||||
|
* Format a paragraph of text to a fixed width, adding spaces for |
||||
|
* indentation to any added line. |
||||
|
*/ |
||||
|
std::string FormatParagraph(const std::string& in, size_t width = 79, size_t indent = 0); |
||||
|
|
||||
|
/**
|
||||
|
* Timing-attack-resistant comparison. |
||||
|
* Takes time proportional to length |
||||
|
* of first argument. |
||||
|
*/ |
||||
|
template <typename T> |
||||
|
bool TimingResistantEqual(const T& a, const T& b) |
||||
|
{ |
||||
|
if (b.size() == 0) return a.size() == 0; |
||||
|
size_t accumulator = a.size() ^ b.size(); |
||||
|
for (size_t i = 0; i < a.size(); i++) |
||||
|
accumulator |= a[i] ^ b[i%b.size()]; |
||||
|
return accumulator == 0; |
||||
|
} |
||||
|
|
||||
|
/** Parse number as fixed point according to JSON number syntax.
|
||||
|
* See http://json.org/number.gif
|
||||
|
* @returns true on success, false on error. |
||||
|
* @note The result must be in the range (-10^18,10^18), otherwise an overflow error will trigger. |
||||
|
*/ |
||||
|
bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); |
||||
|
|
||||
|
/** Convert from one power-of-2 number base to another. */ |
||||
|
template<int frombits, int tobits, bool pad, typename O, typename I> |
||||
|
bool ConvertBits(const O& outfn, I it, I end) { |
||||
|
size_t acc = 0; |
||||
|
size_t bits = 0; |
||||
|
constexpr size_t maxv = (1 << tobits) - 1; |
||||
|
constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1; |
||||
|
while (it != end) { |
||||
|
acc = ((acc << frombits) | *it) & max_acc; |
||||
|
bits += frombits; |
||||
|
while (bits >= tobits) { |
||||
|
bits -= tobits; |
||||
|
outfn((acc >> bits) & maxv); |
||||
|
} |
||||
|
++it; |
||||
|
} |
||||
|
if (pad) { |
||||
|
if (bits) outfn((acc << (tobits - bits)) & maxv); |
||||
|
} else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Converts the given character to its lowercase equivalent. |
||||
|
* This function is locale independent. It only converts uppercase |
||||
|
* characters in the standard 7-bit ASCII range. |
||||
|
* This is a feature, not a limitation. |
||||
|
* |
||||
|
* @param[in] c the character to convert to lowercase. |
||||
|
* @return the lowercase equivalent of c; or the argument |
||||
|
* if no conversion is possible. |
||||
|
*/ |
||||
|
constexpr char ToLower(char c) |
||||
|
{ |
||||
|
return (c >= 'A' && c <= 'Z' ? (c - 'A') + 'a' : c); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Returns the lowercase equivalent of the given string. |
||||
|
* This function is locale independent. It only converts uppercase |
||||
|
* characters in the standard 7-bit ASCII range. |
||||
|
* This is a feature, not a limitation. |
||||
|
* |
||||
|
* @param[in] str the string to convert to lowercase. |
||||
|
* @returns lowercased equivalent of str |
||||
|
*/ |
||||
|
std::string ToLower(const std::string& str); |
||||
|
|
||||
|
/**
|
||||
|
* Converts the given character to its uppercase equivalent. |
||||
|
* This function is locale independent. It only converts lowercase |
||||
|
* characters in the standard 7-bit ASCII range. |
||||
|
* This is a feature, not a limitation. |
||||
|
* |
||||
|
* @param[in] c the character to convert to uppercase. |
||||
|
* @return the uppercase equivalent of c; or the argument |
||||
|
* if no conversion is possible. |
||||
|
*/ |
||||
|
constexpr char ToUpper(char c) |
||||
|
{ |
||||
|
return (c >= 'a' && c <= 'z' ? (c - 'a') + 'A' : c); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Returns the uppercase equivalent of the given string. |
||||
|
* This function is locale independent. It only converts lowercase |
||||
|
* characters in the standard 7-bit ASCII range. |
||||
|
* This is a feature, not a limitation. |
||||
|
* |
||||
|
* @param[in] str the string to convert to uppercase. |
||||
|
* @returns UPPERCASED EQUIVALENT OF str |
||||
|
*/ |
||||
|
std::string ToUpper(const std::string& str); |
||||
|
|
||||
|
/**
|
||||
|
* Capitalizes the first character of the given string. |
||||
|
* This function is locale independent. It only converts lowercase |
||||
|
* characters in the standard 7-bit ASCII range. |
||||
|
* This is a feature, not a limitation. |
||||
|
* |
||||
|
* @param[in] str the string to capitalize. |
||||
|
* @returns string with the first letter capitalized. |
||||
|
*/ |
||||
|
std::string Capitalize(std::string str); |
||||
|
|
||||
|
#endif // BITCOIN_UTIL_STRENCODINGS_H
|
@ -0,0 +1,6 @@ |
|||||
|
// Copyright (c) 2019 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#include "util/string.h" |
@ -0,0 +1,99 @@ |
|||||
|
// Copyright (c) 2019-2020 The Bitcoin Core developers
|
||||
|
// Copyright (c) 2016-2022 The Hush developers
|
||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
||||
|
#ifndef BITCOIN_UTIL_STRING_H |
||||
|
#define BITCOIN_UTIL_STRING_H |
||||
|
|
||||
|
#include "attributes.h" |
||||
|
|
||||
|
#include <algorithm> |
||||
|
#include <array> |
||||
|
#include <cstring> |
||||
|
#include <locale> |
||||
|
#include <sstream> |
||||
|
#include <string> |
||||
|
#include <vector> |
||||
|
|
||||
|
[[nodiscard]] inline std::string TrimString(const std::string& str, const std::string& pattern = " \f\n\r\t\v") |
||||
|
{ |
||||
|
std::string::size_type front = str.find_first_not_of(pattern); |
||||
|
if (front == std::string::npos) { |
||||
|
return std::string(); |
||||
|
} |
||||
|
std::string::size_type end = str.find_last_not_of(pattern); |
||||
|
return str.substr(front, end - front + 1); |
||||
|
} |
||||
|
|
||||
|
[[nodiscard]] inline std::string RemovePrefix(const std::string& str, const std::string& prefix) |
||||
|
{ |
||||
|
if (str.substr(0, prefix.size()) == prefix) { |
||||
|
return str.substr(prefix.size()); |
||||
|
} |
||||
|
return str; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Join a list of items |
||||
|
* |
||||
|
* @param list The list to join |
||||
|
* @param separator The separator |
||||
|
* @param unary_op Apply this operator to each item in the list |
||||
|
*/ |
||||
|
template <typename T, typename BaseType, typename UnaryOp> |
||||
|
auto Join(const std::vector<T>& list, const BaseType& separator, UnaryOp unary_op) |
||||
|
-> decltype(unary_op(list.at(0))) |
||||
|
{ |
||||
|
decltype(unary_op(list.at(0))) ret; |
||||
|
for (size_t i = 0; i < list.size(); ++i) { |
||||
|
if (i > 0) ret += separator; |
||||
|
ret += unary_op(list.at(i)); |
||||
|
} |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
template <typename T> |
||||
|
T Join(const std::vector<T>& list, const T& separator) |
||||
|
{ |
||||
|
return Join(list, separator, [](const T& i) { return i; }); |
||||
|
} |
||||
|
|
||||
|
// Explicit overload needed for c_str arguments, which would otherwise cause a substitution failure in the template above.
|
||||
|
inline std::string Join(const std::vector<std::string>& list, const std::string& separator) |
||||
|
{ |
||||
|
return Join<std::string>(list, separator); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Check if a string does not contain any embedded NUL (\0) characters |
||||
|
*/ |
||||
|
[[nodiscard]] inline bool ValidAsCString(const std::string& str) noexcept |
||||
|
{ |
||||
|
return str.size() == strlen(str.c_str()); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Locale-independent version of std::to_string |
||||
|
*/ |
||||
|
template <typename T> |
||||
|
std::string ToString(const T& t) |
||||
|
{ |
||||
|
std::ostringstream oss; |
||||
|
oss.imbue(std::locale::classic()); |
||||
|
oss << t; |
||||
|
return oss.str(); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Check whether a container begins with the given prefix. |
||||
|
*/ |
||||
|
template <typename T1, size_t PREFIX_LEN> |
||||
|
[[nodiscard]] inline bool HasPrefix(const T1& obj, |
||||
|
const std::array<uint8_t, PREFIX_LEN>& prefix) |
||||
|
{ |
||||
|
return obj.size() >= PREFIX_LEN && |
||||
|
std::equal(std::begin(prefix), std::end(prefix), std::begin(obj)); |
||||
|
} |
||||
|
|
||||
|
#endif // BITCOIN_UTIL_STRENCODINGS_H
|
Loading…
Reference in new issue