diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80f7c9a3d..b6d09a6a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,17 +19,14 @@ jobs: components: rustfmt, clippy - name: Set up cargo cache - uses: actions/cache@v3 + uses: Swatinem/rust-cache@v2 continue-on-error: false with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ runner.os }}-cargo-${{ hashFiles('zeroidc//Cargo.lock') }} + shared-key: ${{ runner.os }}-cargo- + workspaces: | + zeroidc/ + - name: make run: make - name: selftest @@ -54,17 +51,14 @@ jobs: override: true components: rustfmt, clippy - name: Set up cargo cache - uses: actions/cache@v3 + uses: Swatinem/rust-cache@v2 continue-on-error: false with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ runner.os }}-cargo-${{ hashFiles('zeroidc//Cargo.lock') }} + shared-key: ${{ runner.os }}-cargo- + workspaces: | + zeroidc/ + - name: make run: make - name: selftest @@ -89,19 +83,16 @@ jobs: override: true components: rustfmt, clippy - name: Set up cargo cache - uses: actions/cache@v3 + uses: Swatinem/rust-cache@v2 continue-on-error: false with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + key: ${{ runner.os }}-cargo-${{ hashFiles('zeroidc//Cargo.lock') }} + shared-key: ${{ runner.os }}-cargo- + workspaces: | + zeroidc/ + - name: setup msbuild uses: microsoft/setup-msbuild@v1.1.3 - name: msbuild run: | - msbuild windows\ZeroTierOne.sln /m /p:Configuration=Release /property:Platform=x64 /t:ZeroTierOne:Rebuild + msbuild windows\ZeroTierOne.sln /m /p:Configuration=Release /property:Platform=x64 /t:ZeroTierOne diff --git a/.github/workflows/report.sh b/.github/workflows/report.sh new file mode 100755 index 000000000..cc6716213 --- /dev/null +++ b/.github/workflows/report.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +################################################################################ +# Set exit code depending on tool reports # +################################################################################ + +DEFINITELY_LOST=$(cat *test-results/*summary.json | jq .num_definite_bytes_lost) + +cat *test-results/*summary.json + +echo -e "\nBytes of memory definitely lost: $DEFINITELY_LOST" + +if [[ "$DEFINITELY_LOST" -gt 0 ]]; then + exit 1 +fi diff --git a/.github/workflows/validate-1m-linux.sh b/.github/workflows/validate-1m-linux.sh new file mode 100755 index 000000000..3d852a8d7 --- /dev/null +++ b/.github/workflows/validate-1m-linux.sh @@ -0,0 +1,434 @@ +#!/bin/bash + +# This test script joins Earth and pokes some stuff + +TEST_NETWORK=8056c2e21c000001 +RUN_LENGTH=60 +TEST_FINISHED=false +ZTO_VER=$(git describe --tags $(git rev-list --tags --max-count=1)) +ZTO_COMMIT=$(git rev-parse HEAD) +ZTO_COMMIT_SHORT=$(git rev-parse --short HEAD) +TEST_DIR_PREFIX="$ZTO_VER-$ZTO_COMMIT_SHORT-test-results" +echo "Performing test on: $ZTO_VER-$ZTO_COMMIT_SHORT" +TEST_FILEPATH_PREFIX="$TEST_DIR_PREFIX/$ZTO_COMMIT_SHORT" +mkdir $TEST_DIR_PREFIX + +################################################################################ +# Multi-node connectivity and performance test # +################################################################################ +main() { + echo -e "\nRunning test for $RUN_LENGTH seconds" + NS1="ip netns exec ns1" + NS2="ip netns exec ns2" + + ZT1="$NS1 ./zerotier-cli -p9996 -D$(pwd)/node1" + # Specify custom port on one node to ensure that feature works + ZT2="$NS2 ./zerotier-cli -p9997 -D$(pwd)/node2" + + echo -e "\nSetting up network namespaces..." + echo "Setting up ns1" + + ip netns add ns1 + $NS1 ip link set dev lo up + ip link add veth0 type veth peer name veth1 + ip link set veth1 netns ns1 + ip addr add 192.168.0.1/24 dev veth0 + ip link set dev veth0 up + + $NS1 ip addr add 192.168.0.2/24 dev veth1 + $NS1 ip link set dev veth1 up + + # Add default route + $NS1 ip route add default via 192.168.0.1 + + iptables -t nat -A POSTROUTING -s 192.168.0.0/255.255.255.0 \ + -o eth0 -j MASQUERADE + iptables -A FORWARD -i eth0 -o veth0 -j ACCEPT + iptables -A FORWARD -o eth0 -i veth0 -j ACCEPT + + echo "Setting up ns2" + ip netns add ns2 + $NS2 ip link set dev lo up + ip link add veth2 type veth peer name veth3 + ip link set veth3 netns ns2 + ip addr add 192.168.1.1/24 dev veth2 + ip link set dev veth2 up + + $NS2 ip addr add 192.168.1.2/24 dev veth3 + $NS2 ip link set dev veth3 up + $NS2 ip route add default via 192.168.1.1 + + iptables -t nat -A POSTROUTING -s 192.168.1.0/255.255.255.0 \ + -o eth0 -j MASQUERADE + iptables -A FORWARD -i eth0 -o veth2 -j ACCEPT + iptables -A FORWARD -o eth0 -i veth2 -j ACCEPT + + # Allow forwarding + sysctl -w net.ipv4.ip_forward=1 + + echo -e "\nPing from host to namespaces" + + ping -c 3 192.168.0.1 + ping -c 3 192.168.1.1 + + echo -e "\nPing from namespace to host" + + $NS1 ping -c 3 192.168.0.1 + $NS1 ping -c 3 192.168.0.1 + $NS2 ping -c 3 192.168.0.2 + $NS2 ping -c 3 192.168.0.2 + + echo -e "\nPing from ns1 to ns2" + + $NS1 ping -c 3 192.168.0.1 + + echo -e "\nPing from ns2 to ns1" + + $NS2 ping -c 3 192.168.0.1 + + ################################################################################ + # Memory Leak Check # + ################################################################################ + + FILENAME_MEMORY_LOG="$TEST_FILEPATH_PREFIX-memory.log" + + echo -e "\nStarting a ZeroTier instance in each namespace..." + + time_test_start=$(date +%s) + + # Spam the CLI as ZeroTier is starting + spam_cli 100 + + echo "Starting memory leak check" + $NS1 sudo valgrind --demangle=yes --exit-on-first-error=yes \ + --error-exitcode=1 \ + --xml=yes \ + --xml-file=$FILENAME_MEMORY_LOG \ + --leak-check=full \ + ./zerotier-one node1 -p9996 -U >>node_1.log 2>&1 & + + # Second instance, not run in memory profiler + $NS2 sudo ./zerotier-one node2 -U -p9997 >>node_2.log 2>&1 & + + ################################################################################ + # Online Check # + ################################################################################ + + echo "Waiting for ZeroTier to come online before attempting test..." + MAX_WAIT_SECS="${MAX_WAIT_SECS:-120}" + node1_online=false + node2_online=false + both_instances_online=false + time_zt_node1_start=$(date +%s) + time_zt_node2_start=$(date +%s) + + for ((s = 0; s <= MAX_WAIT_SECS; s++)); do + node1_online="$($ZT1 -j info | jq '.online' 2>/dev/null)" + node2_online="$($ZT2 -j info | jq '.online' 2>/dev/null)" + echo "Checking for online status: try #$s, node1:$node1_online, node2:$node2_online" + if [[ "$node1_online" == "true" ]]; then + time_zt_node1_online=$(date +%s) + fi + if [[ "$node2_online" == "true" ]]; then + time_zt_node2_online=$(date +%s) + fi + if [[ "$node2_online" == "true" && "$node1_online" == "true" ]]; then + both_instances_online=true + break + fi + sleep 1 + done + + echo -e "\n\nContents of ZeroTier home paths:" + + ls -lga node1 + tree node1 + ls -lga node2 + tree node2 + + echo -e "\n\nRunning ZeroTier processes:" + echo -e "\nNode 1:" + $NS1 ps aux | grep zerotier-one + echo -e "\nNode 2:" + $NS2 ps aux | grep zerotier-one + + echo -e "\n\nStatus of each instance:" + + echo -e "\n\nNode 1:" + $ZT1 status + echo -e "\n\nNode 2:" + $ZT2 status + + if [[ "$both_instances_online" != "true" ]]; then + echo "One or more instances of ZeroTier failed to come online. Aborting test." + exit 1 + fi + + echo -e "\nJoining networks" + + $ZT1 join $TEST_NETWORK + $ZT2 join $TEST_NETWORK + + sleep 10 + + node1_ip4=$($ZT1 get $TEST_NETWORK ip4) + node2_ip4=$($ZT2 get $TEST_NETWORK ip4) + + echo "node1_ip4=$node1_ip4" + echo "node2_ip4=$node2_ip4" + + echo -e "\nPinging each node" + + PING12_FILENAME="$TEST_FILEPATH_PREFIX-ping-1-to-2.txt" + PING21_FILENAME="$TEST_FILEPATH_PREFIX-ping-2-to-1.txt" + + $NS1 ping -c 16 $node2_ip4 >$PING12_FILENAME + $NS2 ping -c 16 $node1_ip4 >$PING21_FILENAME + + # Parse ping statistics + ping_loss_percent_1_to_2="${ping_loss_percent_1_to_2:-100.0}" + ping_loss_percent_2_to_1="${ping_loss_percent_2_to_1:-100.0}" + + ping_loss_percent_1_to_2=$(cat $PING12_FILENAME | + grep "packet loss" | awk '{print $6}' | sed 's/%//') + ping_loss_percent_2_to_1=$(cat $PING21_FILENAME | + grep "packet loss" | awk '{print $6}' | sed 's/%//') + + # Normalize loss value + ping_loss_percent_1_to_2=$(echo "scale=2; $ping_loss_percent_1_to_2/100.0" | bc) + ping_loss_percent_2_to_1=$(echo "scale=2; $ping_loss_percent_2_to_1/100.0" | bc) + + ################################################################################ + # CLI Check # + ################################################################################ + + echo "Testing basic CLI functionality..." + + spam_cli 10 + + $ZT1 join $TEST_NETWORK + + $ZT1 -h + $ZT1 -v + $ZT1 status + $ZT1 info + $ZT1 listnetworks + $ZT1 peers + $ZT1 listpeers + + $ZT1 -j status + $ZT1 -j info + $ZT1 -j listnetworks + $ZT1 -j peers + $ZT1 -j listpeers + + $ZT1 dump + + $ZT1 get $TEST_NETWORK allowDNS + $ZT1 get $TEST_NETWORK allowDefault + $ZT1 get $TEST_NETWORK allowGlobal + $ZT1 get $TEST_NETWORK allowManaged + $ZT1 get $TEST_NETWORK bridge + $ZT1 get $TEST_NETWORK broadcastEnabled + $ZT1 get $TEST_NETWORK dhcp + $ZT1 get $TEST_NETWORK id + $ZT1 get $TEST_NETWORK mac + $ZT1 get $TEST_NETWORK mtu + $ZT1 get $TEST_NETWORK name + $ZT1 get $TEST_NETWORK netconfRevision + $ZT1 get $TEST_NETWORK nwid + $ZT1 get $TEST_NETWORK portDeviceName + $ZT1 get $TEST_NETWORK portError + $ZT1 get $TEST_NETWORK status + $ZT1 get $TEST_NETWORK type + + # Test an invalid command + $ZT1 get $TEST_NETWORK derpderp + + # TODO: Validate JSON + + ################################################################################ + # Performance Test # + ################################################################################ + + FILENAME_PERF_JSON="$TEST_FILEPATH_PREFIX-iperf.json" + + echo -e "\nBeginning performance test:" + + echo -e "\nStarting server:" + + echo "$NS1 iperf3 -s &" + sleep 1 + + echo -e "\nStarting client:" + sleep 1 + + echo "$NS2 iperf3 --json -c $node1_ip4 > $FILENAME_PERF_JSON" + + cat $FILENAME_PERF_JSON + + ################################################################################ + # Collect ZeroTier dump files # + ################################################################################ + + echo -e "\nCollecting ZeroTier dump files" + + node1_id=$($ZT1 -j status | jq -r .address) + node2_id=$($ZT2 -j status | jq -r .address) + + $ZT1 dump + mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node1_id.txt" + + $ZT2 dump + mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node2_id.txt" + + ################################################################################ + # Let ZeroTier idle long enough for various timers # + ################################################################################ + + echo -e "\nIdling ZeroTier for $RUN_LENGTH seconds..." + sleep $RUN_LENGTH + + echo -e "\nLeaving networks" + + $ZT1 leave $TEST_NETWORK + $ZT2 leave $TEST_NETWORK + + sleep 5 + + ################################################################################ + # Stop test # + ################################################################################ + + echo -e "\nStopping memory check..." + sudo pkill -15 -f valgrind + sleep 10 + + time_test_end=$(date +%s) + + ################################################################################ + # Rename ZeroTier stdout/stderr logs # + ################################################################################ + + mv node_1.log "$TEST_FILEPATH_PREFIX-node-log-$node1_id.txt" + mv node_2.log "$TEST_FILEPATH_PREFIX-node-log-$node2_id.txt" + + ################################################################################ + # Generate report # + ################################################################################ + + cat $FILENAME_MEMORY_LOG + + DEFINITELY_LOST=$(xmlstarlet sel -t -v '/valgrindoutput/error/xwhat' \ + $FILENAME_MEMORY_LOG | grep "definitely" | awk '{print $1;}') + POSSIBLY_LOST=$(xmlstarlet sel -t -v '/valgrindoutput/error/xwhat' \ + $FILENAME_MEMORY_LOG | grep "possibly" | awk '{print $1;}') + + ################################################################################ + # Generate coverage report artifact and summary # + ################################################################################ + + FILENAME_COVERAGE_JSON="$TEST_FILEPATH_PREFIX-coverage.json" + FILENAME_COVERAGE_HTML="$TEST_FILEPATH_PREFIX-coverage.html" + + echo -e "\nGenerating coverage test report..." + + gcovr -r . --exclude ext --json-summary $FILENAME_COVERAGE_JSON \ + --html >$FILENAME_COVERAGE_HTML + + cat $FILENAME_COVERAGE_JSON + + COVERAGE_LINE_COVERED=$(cat $FILENAME_COVERAGE_JSON | jq .line_covered) + COVERAGE_LINE_TOTAL=$(cat $FILENAME_COVERAGE_JSON | jq .line_total) + COVERAGE_LINE_PERCENT=$(cat $FILENAME_COVERAGE_JSON | jq .line_percent) + + COVERAGE_LINE_COVERED="${COVERAGE_LINE_COVERED:-0}" + COVERAGE_LINE_TOTAL="${COVERAGE_LINE_TOTAL:-0}" + COVERAGE_LINE_PERCENT="${COVERAGE_LINE_PERCENT:-0}" + + ################################################################################ + # Default values # + ################################################################################ + + DEFINITELY_LOST="${DEFINITELY_LOST:-0}" + POSSIBLY_LOST="${POSSIBLY_LOST:-0}" + + ################################################################################ + # Summarize and emit json for trend reporting # + ################################################################################ + + FILENAME_SUMMARY="$TEST_FILEPATH_PREFIX-summary.json" + + time_length_test=$((time_test_end - time_test_start)) + time_length_zt_node1_online=$((time_zt_node1_online - time_zt_start)) + time_length_zt_node2_online=$((time_zt_node2_online - time_zt_start)) + #time_length_zt_join=$((time_zt_join_end-time_zt_join_start)) + #time_length_zt_leave=$((time_zt_leave_end-time_zt_leave_start)) + #time_length_zt_can_still_ping=$((time_zt_can_still_ping-time_zt_leave_start)) + + summary=$( + cat <$FILENAME_SUMMARY + cat $FILENAME_SUMMARY +} + +################################################################################ +# CLI Check # +################################################################################ + +spam_cli() { + echo "Spamming CLI..." + # Rapidly spam the CLI with joins/leaves + + MAX_TRIES="${1:-10}" + + for ((s = 0; s <= MAX_TRIES; s++)); do + $ZT1 status + $ZT2 status + sleep 0.1 + done + + SPAM_TRIES=128 + + for ((s = 0; s <= SPAM_TRIES; s++)); do + $ZT1 join $TEST_NETWORK + done + + for ((s = 0; s <= SPAM_TRIES; s++)); do + $ZT1 leave $TEST_NETWORK + done + + for ((s = 0; s <= SPAM_TRIES; s++)); do + $ZT1 leave $TEST_NETWORK + $ZT1 join $TEST_NETWORK + done +} + +main "$@" diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 000000000..c95d8e599 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,56 @@ +on: + push: + workflow_dispatch: + +jobs: + build_ubuntu: + runs-on: ubuntu-latest + steps: + - name: gitconfig + run: | + git config --global core.autocrlf input + + - name: checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-unknown-linux-gnu + override: true + components: rustfmt, clippy + + - name: Set up cargo cache + uses: Swatinem/rust-cache@v2 + continue-on-error: false + with: + key: ${{ runner.os }}-cargo-${{ hashFiles('zeroidc//Cargo.lock') }} + shared-key: ${{ runner.os }}-cargo- + workspaces: | + zeroidc/ + + - name: validate-1m-linux + env: + CC: 'gcc' + CXX: 'g++' + BRANCH: ${{ github.ref_name }} + run: | + sudo apt install -y valgrind xmlstarlet gcovr iperf3 tree + make one ZT_COVERAGE=1 ZT_TRACE=1 + sudo chmod +x ./.github/workflows/validate-1m-linux.sh + sudo ./.github/workflows/validate-1m-linux.sh + + - name: Archive test results + uses: actions/upload-artifact@v3 + with: + name: ${{github.sha}}-test-results + path: "*test-results*" + + - name: final-report + run: | + sudo chmod +x ./.github/workflows/report.sh + sudo ./.github/workflows/report.sh + diff --git a/README.md b/README.md index 66ee9e997..37309bc34 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,6 @@ The base path contains the ZeroTier One service main entry point (`one.cpp`), se - `ext/`: third party libraries, binaries that we ship for convenience on some platforms (Mac and Windows), and installation support files. - `include/`: include files for the ZeroTier core. - `java/`: a JNI wrapper used with our Android mobile app. (The whole Android app is not open source but may be made so in the future.) - - `macui/`: a Macintosh menu-bar app for controlling ZeroTier One, written in Objective C. - `node/`: the ZeroTier virtual Ethernet switch core, which is designed to be entirely separate from the rest of the code and able to be built as a stand-alone OS-independent library. Note to developers: do not use C++11 features in here, since we want this to build on old embedded platforms that lack C++11 support. C++11 can be used elsewhere. - `osdep/`: code to support and integrate with OSes, including platform-specific stuff only built for certain targets. - `rule-compiler/`: JavaScript rules language compiler for defining network-level rules. @@ -105,8 +104,69 @@ On CentOS check `/etc/sysconfig/iptables` for IPTables rules. For other distribu ZeroTier One peers will automatically locate each other and communicate directly over a local wired LAN *if UDP port 9993 inbound is open*. If that port is filtered, they won't be able to see each others' LAN announcement packets. If you're experiencing poor performance between devices on the same physical network, check their firewall settings. Without LAN auto-location peers must attempt "loopback" NAT traversal, which sometimes fails and in any case requires that every packet traverse your external router twice. -Users behind certain types of firewalls and "symmetric" NAT devices may not able able to connect to external peers directly at all. ZeroTier has limited support for port prediction and will *attempt* to traverse symmetric NATs, but this doesn't always work. If P2P connectivity fails you'll be bouncing UDP packets off our relay servers resulting in slower performance. Some NAT router(s) have a configurable NAT mode, and setting this to "full cone" will eliminate this problem. If you do this you may also see a magical improvement for things like VoIP phones, Skype, BitTorrent, WebRTC, certain games, etc., since all of these use NAT traversal techniques similar to ours. +Users behind certain types of firewalls and "symmetric" NAT devices may not be able to connect to external peers directly at all. ZeroTier has limited support for port prediction and will *attempt* to traverse symmetric NATs, but this doesn't always work. If P2P connectivity fails you'll be bouncing UDP packets off our relay servers resulting in slower performance. Some NAT router(s) have a configurable NAT mode, and setting this to "full cone" will eliminate this problem. If you do this you may also see a magical improvement for things like VoIP phones, Skype, BitTorrent, WebRTC, certain games, etc., since all of these use NAT traversal techniques similar to ours. If a firewall between you and the Internet blocks ZeroTier's UDP traffic, you will fall back to last-resort TCP tunneling to rootservers over port 443 (https impersonation). This will work almost anywhere but is *very slow* compared to UDP or direct peer to peer connectivity. Additional help can be found in our [knowledge base](https://zerotier.atlassian.net/wiki/spaces/SD/overview). + +### Prometheus Metrics + +Prometheus Metrics are available at the `/metrics` API endpoint. This endpoint is protected by an API key stored in `authtoken.secret` because of the possibility of information leakage. Information that could be gleaned from the metrics include joined networks and peers your instance is talking to. + +Access control is via the ZeroTier control interface itself and `authtoken.secret`. This can be sent as the `X-ZT1-Auth` HTTP header field or appended to the URL as `?auth=`. You can see the current metrics via `cURL` with the following command: + + // Linux + curl -H "X-ZT1-Auth: $(sudo cat /var/lib/zerotier-one/authtoken.secret)" http://localhost:9993/metrics + + // macOS + curl -H "X-XT1-Auth: $(sudo cat /Library/Application\ Support/ZeroTier/One/authtoken.secret)" http://localhost:9993/metrics + + // Windows PowerShell (Admin) + Invoke-RestMethod -Headers @{'X-ZT1-Auth' = "$(Get-Content C:\ProgramData\ZeroTier\One\authtoken.secret)"; } -Uri http://localhost:9993/metrics + +To configure a scrape job in Prometheus on the machine ZeroTier is running on, add this to your Prometheus `scrape_config`: + + - job_name: zerotier-one + honor_labels: true + scrape_interval: 15s + metrics_path: /metrics + static_configs: + - targets: + - 127.0.0.1:9993 + labels: + group: zerotier-one + params: + auth: + - $YOUR_AUTHTOKEN_SECRET + +If your Prometheus instance is remote from the machine ZeroTier instance, you'll have to edit your `local.conf` file to allow remote access to the API control port. If your local lan is `10.0.0.0/24`, edit your `local.conf` as follows: + + { + "settings": { + "allowManagementFrom:" ["10.0.0.0/24"] + } + } + +Substitute your actual network IP ranges as necessary. + +It's also possible to access the metrics & control port over the ZeroTier network itself via the same method shown above. Just add the address range of your ZeroTier network to the list. NOTE: Using this method means that anyone with your auth token can control your ZeroTier instance, including leaving & joining other networks. + +If neither of these methods are desirable, it is probably possible to distribute metrics via [Prometheus Proxy](https://github.com/pambrose/prometheus-proxy) or some other tool. Note: We have not tested this internally, but will probably work with the correct configuration. + +#### Available Metrics + +| Metric Name | Labels | Metric Type | Description | +| --- | --- | --- | --- | +| zt_packet | packet_type, direction | Counter | ZeroTier packet type counts | +| zt_packet_error | error_type, direction | Counter | ZeroTier packet errors| +| zt_data | protocol, direction | Counter | number of bytes ZeroTier has transmitted or received | +| zt_num_networks | | Gauge | number of networks this instance is joined to | +| zt_network_multicast_groups_subscribed | network_id | Gauge | number of multicast groups networks are subscribed to | +| zt_network_packets | network_id, direction | Counter | number of incoming/outgoing packets per network | +| zt_peer_latency | node_id | Histogram | peer latency (ms) | +| zt_peer_path_count | node_id, status | Gauge | number of paths to peer | +| zt_peer_packets | node_id, direction | Counter | number of packets to/from a peer | +| zt_peer_packet_errors | node_id | Counter | number of incoming packet errors from a peer | + +If there are other metrics you'd like to see tracked, ask us in an Issue or send us a Pull Request! diff --git a/controller/DB.cpp b/controller/DB.cpp index 1de2fbe8b..b1c820144 100644 --- a/controller/DB.cpp +++ b/controller/DB.cpp @@ -108,16 +108,17 @@ DB::~DB() {} bool DB::get(const uint64_t networkId,nlohmann::json &network) { waitForReady(); + Metrics::db_get_network++; std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); auto nwi = _networks.find(networkId); if (nwi == _networks.end()) return false; nw = nwi->second; } { - std::lock_guard l2(nw->lock); + std::shared_lock l2(nw->lock); network = nw->config; } return true; @@ -126,16 +127,17 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network) bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member) { waitForReady(); + Metrics::db_get_network_and_member++; std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); auto nwi = _networks.find(networkId); if (nwi == _networks.end()) return false; nw = nwi->second; } { - std::lock_guard l2(nw->lock); + std::shared_lock l2(nw->lock); network = nw->config; auto m = nw->members.find(memberId); if (m == nw->members.end()) @@ -148,16 +150,17 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,NetworkSummaryInfo &info) { waitForReady(); + Metrics::db_get_network_and_member_and_summary++; std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); auto nwi = _networks.find(networkId); if (nwi == _networks.end()) return false; nw = nwi->second; } { - std::lock_guard l2(nw->lock); + std::shared_lock l2(nw->lock); network = nw->config; _fillSummaryInfo(nw,info); auto m = nw->members.find(memberId); @@ -171,16 +174,17 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vector &members) { waitForReady(); + Metrics::db_get_member_list++; std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); auto nwi = _networks.find(networkId); if (nwi == _networks.end()) return false; nw = nwi->second; } { - std::lock_guard l2(nw->lock); + std::shared_lock l2(nw->lock); network = nw->config; for(auto m=nw->members.begin();m!=nw->members.end();++m) { members.push_back(m->second); @@ -192,13 +196,15 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vector &networks) { waitForReady(); - std::lock_guard l(_networks_l); + Metrics::db_get_member_list++; + std::shared_lock l(_networks_l); for(auto n=_networks.begin();n!=_networks.end();++n) networks.insert(n->first); } void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners) { + Metrics::db_member_change++; uint64_t memberId = 0; uint64_t networkId = 0; bool isAuth = false; @@ -210,14 +216,14 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no networkId = OSUtils::jsonIntHex(old["nwid"],0ULL); if ((memberId)&&(networkId)) { { - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); auto nw2 = _networks.find(networkId); if (nw2 != _networks.end()) { nw = nw2->second; } } if (nw) { - std::lock_guard l(nw->lock); + std::unique_lock l(nw->lock); if (OSUtils::jsonBool(old["activeBridge"],false)) { nw->activeBridgeMembers.erase(memberId); } @@ -247,7 +253,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no networkId = OSUtils::jsonIntHex(memberConfig["nwid"],0ULL); if ((!memberId)||(!networkId)) return; - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); std::shared_ptr<_Network> &nw2 = _networks[networkId]; if (!nw2) nw2.reset(new _Network); @@ -255,7 +261,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no } { - std::lock_guard l(nw->lock); + std::unique_lock l(nw->lock); nw->members[memberId] = memberConfig; @@ -288,18 +294,18 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no } if (notifyListeners) { - std::lock_guard ll(_changeListeners_l); + std::unique_lock ll(_changeListeners_l); for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) { (*i)->onNetworkMemberUpdate(this,networkId,memberId,memberConfig); } } } else if (memberId) { if (nw) { - std::lock_guard l(nw->lock); + std::unique_lock l(nw->lock); nw->members.erase(memberId); } if (networkId) { - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); auto er = _networkByMember.equal_range(memberId); for(auto i=er.first;i!=er.second;++i) { if (i->second == networkId) { @@ -329,7 +335,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no } if ((notifyListeners)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId))) { - std::lock_guard ll(_changeListeners_l); + std::unique_lock ll(_changeListeners_l); for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) { (*i)->onNetworkMemberDeauthorize(this,networkId,memberId); } @@ -338,6 +344,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners) { + Metrics::db_network_change++; if (notifyListeners) { if (old.is_object() && old.contains("id") && networkConfig.is_object() && networkConfig.contains("id")) { Metrics::network_changes++; @@ -354,18 +361,18 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool if (networkId) { std::shared_ptr<_Network> nw; { - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); std::shared_ptr<_Network> &nw2 = _networks[networkId]; if (!nw2) nw2.reset(new _Network); nw = nw2; } { - std::lock_guard l2(nw->lock); + std::unique_lock l2(nw->lock); nw->config = networkConfig; } if (notifyListeners) { - std::lock_guard ll(_changeListeners_l); + std::unique_lock ll(_changeListeners_l); for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) { (*i)->onNetworkUpdate(this,networkId,networkConfig); } @@ -375,7 +382,7 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool const std::string ids = old["id"]; const uint64_t networkId = Utils::hexStrToU64(ids.c_str()); if (networkId) { - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); _networks.erase(networkId); } } diff --git a/controller/DB.hpp b/controller/DB.hpp index f70d66e03..89610f7c5 100644 --- a/controller/DB.hpp +++ b/controller/DB.hpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include @@ -109,7 +109,7 @@ public: inline bool hasNetwork(const uint64_t networkId) const { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); return (_networks.find(networkId) != _networks.end()); } @@ -124,7 +124,7 @@ public: inline void each(F f) { nlohmann::json nullJson; - std::lock_guard lck(_networks_l); + std::unique_lock lck(_networks_l); for(auto nw=_networks.begin();nw!=_networks.end();++nw) { f(nw->first,nw->second->config,0,nullJson); // first provide network with 0 for member ID for(auto m=nw->second->members.begin();m!=nw->second->members.end();++m) { @@ -142,7 +142,7 @@ public: inline void addListener(DB::ChangeListener *const listener) { - std::lock_guard l(_changeListeners_l); + std::unique_lock l(_changeListeners_l); _changeListeners.push_back(listener); } @@ -178,7 +178,7 @@ protected: std::unordered_set authorizedMembers; std::unordered_set allocatedIps; int64_t mostRecentDeauthTime; - std::mutex lock; + std::shared_mutex lock; }; virtual void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners); @@ -188,8 +188,8 @@ protected: std::vector _changeListeners; std::unordered_map< uint64_t,std::shared_ptr<_Network> > _networks; std::unordered_multimap< uint64_t,uint64_t > _networkByMember; - mutable std::mutex _changeListeners_l; - mutable std::mutex _networks_l; + mutable std::shared_mutex _changeListeners_l; + mutable std::shared_mutex _networks_l; }; } // namespace ZeroTier diff --git a/controller/DBMirrorSet.cpp b/controller/DBMirrorSet.cpp index fd7f32a22..9372eef65 100644 --- a/controller/DBMirrorSet.cpp +++ b/controller/DBMirrorSet.cpp @@ -15,9 +15,12 @@ namespace ZeroTier { -DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener) : - _listener(listener), - _running(true) +DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener) + : _listener(listener) + , _running(true) + , _syncCheckerThread() + , _dbs() + , _dbs_l() { _syncCheckerThread = std::thread([this]() { for(;;) { @@ -29,7 +32,7 @@ DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener) : std::vector< std::shared_ptr > dbs; { - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); if (_dbs.size() <= 1) continue; // no need to do this if there's only one DB, so skip the iteration dbs = _dbs; @@ -76,7 +79,7 @@ DBMirrorSet::~DBMirrorSet() bool DBMirrorSet::hasNetwork(const uint64_t networkId) const { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->hasNetwork(networkId)) return true; @@ -86,7 +89,7 @@ bool DBMirrorSet::hasNetwork(const uint64_t networkId) const bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->get(networkId,network)) { return true; @@ -97,7 +100,7 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network) bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->get(networkId,network,memberId,member)) return true; @@ -107,7 +110,7 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uin bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,DB::NetworkSummaryInfo &info) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->get(networkId,network,memberId,member,info)) return true; @@ -117,7 +120,7 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uin bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,std::vector &members) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if ((*d)->get(networkId,network,members)) return true; @@ -127,7 +130,7 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,std::vect AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { AuthInfo info = (*d)->getSSOAuthInfo(member, redirectURL); if (info.enabled) { @@ -139,7 +142,7 @@ AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json &member, const std::st void DBMirrorSet::networks(std::set &networks) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { (*d)->networks(networks); } @@ -148,7 +151,7 @@ void DBMirrorSet::networks(std::set &networks) bool DBMirrorSet::waitForReady() { bool r = false; - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { r |= (*d)->waitForReady(); } @@ -157,7 +160,7 @@ bool DBMirrorSet::waitForReady() bool DBMirrorSet::isReady() { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if (!(*d)->isReady()) return false; @@ -169,7 +172,7 @@ bool DBMirrorSet::save(nlohmann::json &record,bool notifyListeners) { std::vector< std::shared_ptr > dbs; { - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); dbs = _dbs; } if (notifyListeners) { @@ -189,7 +192,7 @@ bool DBMirrorSet::save(nlohmann::json &record,bool notifyListeners) void DBMirrorSet::eraseNetwork(const uint64_t networkId) { - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { (*d)->eraseNetwork(networkId); } @@ -197,7 +200,7 @@ void DBMirrorSet::eraseNetwork(const uint64_t networkId) void DBMirrorSet::eraseMember(const uint64_t networkId,const uint64_t memberId) { - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { (*d)->eraseMember(networkId,memberId); } @@ -205,7 +208,7 @@ void DBMirrorSet::eraseMember(const uint64_t networkId,const uint64_t memberId) void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) { - std::lock_guard l(_dbs_l); + std::shared_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { (*d)->nodeIsOnline(networkId,memberId,physicalAddress); } @@ -214,7 +217,7 @@ void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId, void DBMirrorSet::onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network) { nlohmann::json record(network); - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if (d->get() != db) { (*d)->save(record,false); @@ -226,7 +229,7 @@ void DBMirrorSet::onNetworkUpdate(const void *db,uint64_t networkId,const nlohma void DBMirrorSet::onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member) { nlohmann::json record(member); - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); for(auto d=_dbs.begin();d!=_dbs.end();++d) { if (d->get() != db) { (*d)->save(record,false); diff --git a/controller/DBMirrorSet.hpp b/controller/DBMirrorSet.hpp index 883c98fd7..e33b63ec9 100644 --- a/controller/DBMirrorSet.hpp +++ b/controller/DBMirrorSet.hpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include @@ -56,7 +56,7 @@ public: inline void addDB(const std::shared_ptr &db) { db->addListener(this); - std::lock_guard l(_dbs_l); + std::unique_lock l(_dbs_l); _dbs.push_back(db); } @@ -65,7 +65,7 @@ private: std::atomic_bool _running; std::thread _syncCheckerThread; std::vector< std::shared_ptr< DB > > _dbs; - mutable std::mutex _dbs_l; + mutable std::shared_mutex _dbs_l; }; } // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 2ccc16b8c..b60c375c4 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #ifndef _WIN32 #include @@ -459,15 +460,51 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) } // anonymous namespace -EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc) : - _startTime(OSUtils::now()), - _listenPort(listenPort), - _node(node), - _ztPath(ztPath), - _path(dbPath), - _sender((NetworkController::Sender *)0), - _db(this), - _rc(rc) +EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc) + : _startTime(OSUtils::now()) + , _listenPort(listenPort) + , _node(node) + , _ztPath(ztPath) + , _path(dbPath) + , _signingId() + , _signingIdAddressString() + , _sender((NetworkController::Sender *)0) + , _db(this) + , _queue() + , _threads() + , _threads_l() + , _memberStatus() + , _memberStatus_l() + , _expiringSoon() + , _expiringSoon_l() + , _rc(rc) + , _ssoExpiryRunning(true) + , _ssoExpiry(std::thread(&EmbeddedNetworkController::_ssoExpiryThread, this)) + +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + , _member_status_lookup{"nc_member_status_lookup",""} + , _member_status_lookup_count{"nc_member_status_lookup_count",""} + , _node_is_online{"nc_node_is_online",""} + , _node_is_online_count{"nc_node_is_online_count",""} + , _get_and_init_member{"nc_get_and_init_member",""} + , _get_and_init_member_count{"nc_get_and_init_member_count",""} + , _have_identity{"nc_have_identity",""} + , _have_identity_count{"nc_have_identity_count",""} + , _determine_auth{"nc_determine_auth",""} + , _determine_auth_count{"nc_determine_auth_count",""} + , _sso_check{"nc_sso_check",""} + , _sso_check_count{"nc_sso_check_count",""} + , _auth_check{"nc_auth_check",""} + , _auth_check_count{"nc_auth_check_count",""} + , _json_schlep{"nc_json_schlep",""} + , _json_schlep_count{"nc_json_schlep_count",""} + , _issue_certificate{"nc_issue_certificate", ""} + , _issue_certificate_count{"nc_issue_certificate_count",""} + , _save_member{"nc_save_member",""} + , _save_member_count{"nc_save_member_count",""} + , _send_netconf{"nc_send_netconf2",""} + , _send_netconf_count{"nc_send_netconf2_count",""} +#endif { } @@ -475,8 +512,11 @@ EmbeddedNetworkController::~EmbeddedNetworkController() { std::lock_guard l(_threads_l); _queue.stop(); - for(auto t=_threads.begin();t!=_threads.end();++t) + for(auto t=_threads.begin();t!=_threads.end();++t) { t->join(); + } + _ssoExpiryRunning = false; + _ssoExpiry.join(); } void EmbeddedNetworkController::setSSORedirectURL(const std::string &url) { @@ -543,6 +583,18 @@ void EmbeddedNetworkController::request( if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) return; _startThreads(); + + const int64_t now = OSUtils::now(); + + if (requestPacketId) { + std::lock_guard l(_memberStatus_l); + _MemberStatus &ms = _memberStatus[_MemberStatusKey(nwid,identity.address().toInt())]; + if ((now - ms.lastRequestTime) <= ZT_NETCONF_MIN_REQUEST_PERIOD) { + return; + } + ms.lastRequestTime = now; + } + _RQEntry *qe = new _RQEntry; qe->nwid = nwid; qe->requestPacketId = requestPacketId; @@ -553,593 +605,528 @@ void EmbeddedNetworkController::request( _queue.post(qe); } -unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) +std::string EmbeddedNetworkController::networkUpdateFromPostData(uint64_t networkID, const std::string &body) { - if ((!path.empty())&&(path[0] == "network")) { + json b = OSUtils::jsonParse(body); - if ((path.size() >= 2)&&(path[1].length() == 16)) { - const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); - json network; - if (!_db.get(nwid,network)) - return 404; + char nwids[24]; + OSUtils::ztsnprintf(nwids, sizeof(nwids), "%.16llx", networkID); - if (path.size() >= 3) { + json network; + _db.get(networkID, network); + DB::initNetwork(network); + if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],""); + if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true); + if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false); + if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL); + if (b.count("mtu")) network["mtu"] = std::max(std::min((unsigned int)OSUtils::jsonInt(b["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU); - if (path[2] == "member") { - - if (path.size() >= 4) { - // Get member - - const uint64_t address = Utils::hexStrToU64(path[3].c_str()); - json member; - if (!_db.get(nwid,network,address,member)) - return 404; - responseBody = OSUtils::jsonDump(member); - responseContentType = "application/json"; - - } else { - // List members and their revisions - - responseBody = "{"; - std::vector members; - if (_db.get(nwid,network,members)) { - responseBody.reserve((members.size() + 2) * 32); - std::string mid; - for(auto member=members.begin();member!=members.end();++member) { - mid = OSUtils::jsonString((*member)["id"], ""); - char tmp[128]; - OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s\"%s\":%llu",(responseBody.length() > 1) ? "," : "",mid.c_str(),(unsigned long long)OSUtils::jsonInt((*member)["revision"],0)); - responseBody.append(tmp); - } - } - responseBody.push_back('}'); - responseContentType = "application/json"; - - } - return 200; - - } // else 404 - - } else { - // Get network - - responseBody = OSUtils::jsonDump(network); - responseContentType = "application/json"; - return 200; - - } - } else if (path.size() == 1) { - // List networks - - std::set networkIds; - _db.networks(networkIds); - char tmp[64]; - responseBody = "["; - responseBody.reserve((networkIds.size() + 1) * 24); - for(std::set::const_iterator i(networkIds.begin());i!=networkIds.end();++i) { - if (responseBody.length() > 1) - responseBody.push_back(','); - OSUtils::ztsnprintf(tmp,sizeof(tmp),"\"%.16llx\"",(unsigned long long)*i); - responseBody.append(tmp); - } - responseBody.push_back(']'); - responseContentType = "application/json"; - - return 200; - - } // else 404 - - } else { - // Controller status - - char tmp[4096]; - const bool dbOk = _db.isReady(); - OSUtils::ztsnprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"databaseReady\": %s\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),dbOk ? "true" : "false"); - responseBody = tmp; - responseContentType = "application/json"; - return dbOk ? 200 : 503; - - } - - return 404; -} - -unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) -{ - if (path.empty()) - return 404; - - json b; - try { - b = OSUtils::jsonParse(body); - if (!b.is_object()) { - responseBody = "{ \"message\": \"body is not a JSON object\" }"; - responseContentType = "application/json"; - return 400; + if (b.count("remoteTraceTarget")) { + const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],"")); + if (rtt.length() == 10) { + network["remoteTraceTarget"] = rtt; + } else { + network["remoteTraceTarget"] = json(); } - } catch ( ... ) { - responseBody = "{ \"message\": \"body JSON is invalid\" }"; - responseContentType = "application/json"; - return 400; } - const int64_t now = OSUtils::now(); + if (b.count("remoteTraceLevel")) network["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL); - if (path[0] == "network") { + if (b.count("v4AssignMode")) { + json nv4m; + json &v4m = b["v4AssignMode"]; + if (v4m.is_string()) { // backward compatibility + nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt"); + } else if (v4m.is_object()) { + nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false); + } else nv4m["zt"] = false; + network["v4AssignMode"] = nv4m; + } - if ((path.size() >= 2)&&(path[1].length() == 16)) { - uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); - char nwids[24]; - OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + if (b.count("v6AssignMode")) { + json nv6m; + json &v6m = b["v6AssignMode"]; + if (!nv6m.is_object()) nv6m = json::object(); + if (v6m.is_string()) { // backward compatibility + std::vector v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","","")); + std::sort(v6ms.begin(),v6ms.end()); + v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end()); + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + for(std::vector::iterator i(v6ms.begin());i!=v6ms.end();++i) { + if (*i == "rfc4193") + nv6m["rfc4193"] = true; + else if (*i == "zt") + nv6m["zt"] = true; + else if (*i == "6plane") + nv6m["6plane"] = true; + } + } else if (v6m.is_object()) { + if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false); + if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false); + if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false); + } else { + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + } + network["v6AssignMode"] = nv6m; + } - if (path.size() >= 3) { - - if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { - uint64_t address = Utils::hexStrToU64(path[3].c_str()); - char addrs[24]; - OSUtils::ztsnprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address); - - json member,network; - _db.get(nwid,network,address,member); - DB::initMember(member); - - try { - if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"], false); - if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"], false); - if (b.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = (uint64_t)OSUtils::jsonInt(b["authenticationExpiryTime"], 0ULL); - if (b.count("authenticationURL")) member["authenticationURL"] = OSUtils::jsonString(b["authenticationURL"], ""); - - if (b.count("remoteTraceTarget")) { - const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],"")); - if (rtt.length() == 10) { - member["remoteTraceTarget"] = rtt; - } else { - member["remoteTraceTarget"] = json(); - } + if (b.count("routes")) { + json &rts = b["routes"]; + if (rts.is_array()) { + json nrts = json::array(); + for(unsigned long i=0;i().c_str()); + InetAddress v; + if (via.is_string()) v.fromString(via.get().c_str()); + if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { + json tmp; + char tmp2[64]; + tmp["target"] = t.toString(tmp2); + if (v.ss_family == t.ss_family) + tmp["via"] = v.toIpString(tmp2); + else tmp["via"] = json(); + nrts.push_back(tmp); + if (nrts.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; } - if (b.count("remoteTraceLevel")) member["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL); - - if (b.count("authorized")) { - const bool newAuth = OSUtils::jsonBool(b["authorized"],false); - if (newAuth != OSUtils::jsonBool(member["authorized"],false)) { - member["authorized"] = newAuth; - member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now; - if (newAuth) { - member["lastAuthorizedCredentialType"] = "api"; - member["lastAuthorizedCredential"] = json(); - } - } - } - - if (b.count("ipAssignments")) { - json &ipa = b["ipAssignments"]; - if (ipa.is_array()) { - json mipa(json::array()); - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - member["ipAssignments"] = mipa; - } - } - - if (b.count("tags")) { - json &tags = b["tags"]; - if (tags.is_array()) { - std::map mtags; - for(unsigned long i=0;i::iterator t(mtags.begin());t!=mtags.end();++t) { - json ta = json::array(); - ta.push_back(t->first); - ta.push_back(t->second); - mtagsa.push_back(ta); - if (mtagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - member["tags"] = mtagsa; - } - } - - if (b.count("capabilities")) { - json &capabilities = b["capabilities"]; - if (capabilities.is_array()) { - json mcaps = json::array(); - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - std::sort(mcaps.begin(),mcaps.end()); - mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end()); - member["capabilities"] = mcaps; - } - } - } catch ( ... ) { - responseBody = "{ \"message\": \"exception while processing parameters in JSON body\" }"; - responseContentType = "application/json"; - return 400; } + } + } + network["routes"] = nrts; + } + } - member["id"] = addrs; - member["address"] = addrs; // legacy - member["nwid"] = nwids; - - DB::cleanMember(member); - _db.save(member,true); - responseBody = OSUtils::jsonDump(member); - responseContentType = "application/json"; - - return 200; - } // else 404 - - } else { - // POST to network ID - - // Magic ID ending with ______ picks a random unused network ID - if (path[1].substr(10) == "______") { - nwid = 0; - uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL; - uint64_t nwidPostfix = 0; - for(unsigned long k=0;k<100000;++k) { // sanity limit on trials - Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); - uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); - if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; - if (!_db.hasNetwork(tryNwid)) { - nwid = tryNwid; + if (b.count("ipAssignmentPools")) { + json &ipp = b["ipAssignmentPools"]; + if (ipp.is_array()) { + json nipp = json::array(); + for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) break; - } } - if (!nwid) - return 503; } - OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); - - json network; - _db.get(nwid,network); - DB::initNetwork(network); - - try { - if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],""); - if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true); - if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false); - if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL); - if (b.count("mtu")) network["mtu"] = std::max(std::min((unsigned int)OSUtils::jsonInt(b["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU); - - if (b.count("remoteTraceTarget")) { - const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],"")); - if (rtt.length() == 10) { - network["remoteTraceTarget"] = rtt; - } else { - network["remoteTraceTarget"] = json(); - } - } - if (b.count("remoteTraceLevel")) network["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL); - - if (b.count("v4AssignMode")) { - json nv4m; - json &v4m = b["v4AssignMode"]; - if (v4m.is_string()) { // backward compatibility - nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt"); - } else if (v4m.is_object()) { - nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false); - } else nv4m["zt"] = false; - network["v4AssignMode"] = nv4m; - } - - if (b.count("v6AssignMode")) { - json nv6m; - json &v6m = b["v6AssignMode"]; - if (!nv6m.is_object()) nv6m = json::object(); - if (v6m.is_string()) { // backward compatibility - std::vector v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","","")); - std::sort(v6ms.begin(),v6ms.end()); - v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end()); - nv6m["rfc4193"] = false; - nv6m["zt"] = false; - nv6m["6plane"] = false; - for(std::vector::iterator i(v6ms.begin());i!=v6ms.end();++i) { - if (*i == "rfc4193") - nv6m["rfc4193"] = true; - else if (*i == "zt") - nv6m["zt"] = true; - else if (*i == "6plane") - nv6m["6plane"] = true; - } - } else if (v6m.is_object()) { - if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false); - if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false); - if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false); - } else { - nv6m["rfc4193"] = false; - nv6m["zt"] = false; - nv6m["6plane"] = false; - } - network["v6AssignMode"] = nv6m; - } - - if (b.count("routes")) { - json &rts = b["routes"]; - if (rts.is_array()) { - json nrts = json::array(); - for(unsigned long i=0;i().c_str()); - InetAddress v; - if (via.is_string()) v.fromString(via.get().c_str()); - if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { - json tmp; - char tmp2[64]; - tmp["target"] = t.toString(tmp2); - if (v.ss_family == t.ss_family) - tmp["via"] = v.toIpString(tmp2); - else tmp["via"] = json(); - nrts.push_back(tmp); - if (nrts.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - } - } - network["routes"] = nrts; - } - } - - if (b.count("ipAssignmentPools")) { - json &ipp = b["ipAssignmentPools"]; - if (ipp.is_array()) { - json nipp = json::array(); - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - } - network["ipAssignmentPools"] = nipp; - } - } - - if (b.count("rules")) { - json &rules = b["rules"]; - if (rules.is_array()) { - json nrules = json::array(); - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - } - network["rules"] = nrules; - } - } - - if (b.count("authTokens")) { - json &authTokens = b["authTokens"]; - if (authTokens.is_object()) { - json nat; - for(json::iterator t(authTokens.begin());t!=authTokens.end();++t) { - if ((t.value().is_number())&&(t.value() >= 0)) - nat[t.key()] = t.value(); - } - network["authTokens"] = nat; - } else { - network["authTokens"] = {{}}; - } - } - - if (b.count("capabilities")) { - json &capabilities = b["capabilities"]; - if (capabilities.is_array()) { - std::map< uint64_t,json > ncaps; - for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - } - } - } - ncap["rules"] = nrules; - - ncaps[capId] = ncap; - } - } - - json ncapsa = json::array(); - for(std::map< uint64_t,json >::iterator c(ncaps.begin());c!=ncaps.end();++c) { - ncapsa.push_back(c->second); - if (ncapsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - network["capabilities"] = ncapsa; - } - } - - if (b.count("tags")) { - json &tags = b["tags"]; - if (tags.is_array()) { - std::map< uint64_t,json > ntags; - for(unsigned long i=0;i::iterator t(ntags.begin());t!=ntags.end();++t) { - ntagsa.push_back(t->second); - if (ntagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) - break; - } - network["tags"] = ntagsa; - } - } - - if (b.count("dns")) { - json &dns = b["dns"]; - if (dns.is_object()) { - json nd; - - nd["domain"] = dns["domain"]; - - json &srv = dns["servers"]; - if (srv.is_array()) { - json ns = json::array(); - for(unsigned int i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + } + } + network["rules"] = nrules; + } + } + + if (b.count("authTokens")) { + json &authTokens = b["authTokens"]; + if (authTokens.is_object()) { + json nat; + for(json::iterator t(authTokens.begin());t!=authTokens.end();++t) { + if ((t.value().is_number())&&(t.value() >= 0)) + nat[t.key()] = t.value(); + } + network["authTokens"] = nat; + } else { + network["authTokens"] = {{}}; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + std::map< uint64_t,json > ncaps; + for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + } + } + } + ncap["rules"] = nrules; + + ncaps[capId] = ncap; + } + } + + json ncapsa = json::array(); + for(std::map< uint64_t,json >::iterator c(ncaps.begin());c!=ncaps.end();++c) { + ncapsa.push_back(c->second); + if (ncapsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + network["capabilities"] = ncapsa; + } + } + + if (b.count("tags")) { + json &tags = b["tags"]; + if (tags.is_array()) { + std::map< uint64_t,json > ntags; + for(unsigned long i=0;i::iterator t(ntags.begin());t!=ntags.end();++t) { + ntagsa.push_back(t->second); + if (ntagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + network["tags"] = ntagsa; + } + } + + if (b.count("dns")) { + json &dns = b["dns"]; + if (dns.is_object()) { + json nd; + + nd["domain"] = dns["domain"]; + + json &srv = dns["servers"]; + if (srv.is_array()) { + json ns = json::array(); + for(unsigned int i=0;i &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) +void EmbeddedNetworkController::configureHTTPControlPlane( + httplib::Server &s, + const std::function setContent) { - if (path.empty()) - return 404; + s.Get("/controller/network", [&, setContent](const httplib::Request &req, httplib::Response &res) { + std::set networkIds; + _db.networks(networkIds); + char tmp[64]; - if (path[0] == "network") { - if ((path.size() >= 2)&&(path[1].length() == 16)) { - const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); - if (path.size() >= 3) { - if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { - const uint64_t address = Utils::hexStrToU64(path[3].c_str()); + auto out = json::array(); + for(std::set::const_iterator i(networkIds.begin()); i != networkIds.end(); ++i) { + OSUtils::ztsnprintf(tmp, sizeof(tmp), "%.16llx", *i); + out.push_back(tmp); + } - json network,member; - _db.get(nwid,network,address,member); - _db.eraseMember(nwid, address); + setContent(req, res, out.dump()); + }); - { - std::lock_guard l(_memberStatus_l); - _memberStatus.erase(_MemberStatusKey(nwid,address)); - } + s.Get("/controller/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1]; + uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); + json network; + if (!_db.get(nwid, network)) { + res.status = 404; + return; + } - if (!member.size()) - return 404; - responseBody = OSUtils::jsonDump(member); - responseContentType = "application/json"; - return 200; - } - } else { - json network; - _db.get(nwid,network); - _db.eraseNetwork(nwid); + setContent(req, res, network.dump()); + }); - { - std::lock_guard l(_memberStatus_l); - for(auto i=_memberStatus.begin();i!=_memberStatus.end();) { - if (i->first.networkId == nwid) - _memberStatus.erase(i++); - else ++i; - } - } - - if (!network.size()) - return 404; - responseBody = OSUtils::jsonDump(network); - responseContentType = "application/json"; - return 200; + auto createNewNetwork = [&, setContent](const httplib::Request &req, httplib::Response &res) { + fprintf(stderr, "creating new network (new style)\n"); + uint64_t nwid = 0; + uint64_t nwidPrefix = (Utils::hexStrToU64(_signingIdAddressString.c_str()) << 24) & 0xffffffffff000000ULL; + uint64_t nwidPostfix = 0; + for(unsigned long k=0;k<100000;++k) { // sanity limit on trials + Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); + uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); + if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; + if (!_db.hasNetwork(tryNwid)) { + nwid = tryNwid; + break; } - } // else 404 + } + if (!nwid) { + res.status = 503; + return; + } - } // else 404 + setContent(req, res, networkUpdateFromPostData(nwid, req.body)); + }; + s.Put("/controller/network", createNewNetwork); + s.Post("/controller/network", createNewNetwork); - return 404; + auto createNewNetworkOldAndBusted = [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto inID = req.matches[1].str(); + + if (inID != _signingIdAddressString) { + res.status = 400; + return; + } + + uint64_t nwid = 0; + uint64_t nwidPrefix = (Utils::hexStrToU64(inID.c_str()) << 24) & 0xffffffffff000000ULL; + uint64_t nwidPostfix = 0; + for(unsigned long k=0;k<100000;++k) { // sanity limit on trials + Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); + uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); + if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; + if (!_db.hasNetwork(tryNwid)) { + nwid = tryNwid; + break; + } + } + if (!nwid) { + res.status = 503; + return; + } + setContent(req, res, networkUpdateFromPostData(nwid, req.body)); + }; + s.Put("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted); + s.Post("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted); + + s.Delete("/controller/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1].str(); + uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); + + json network; + if (!_db.get(nwid,network)) { + res.status = 404; + return; + } + + _db.eraseNetwork(nwid); + setContent(req, res, network.dump()); + }); + + s.Get("/controller/network/([0-9a-fA-F]{16})/member", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1]; + uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); + json network; + if (!_db.get(nwid, network)) { + res.status = 404; + return; + } + + json out = json::array(); + std::vector memTmp; + if (_db.get(nwid, network, memTmp)) { + for (auto m = memTmp.begin(); m != memTmp.end(); ++m) { + int revision = OSUtils::jsonInt((*m)["revision"], 0); + std::string id = OSUtils::jsonString((*m)["id"], ""); + if (id.length() == 10) { + json tmp = json::object(); + tmp[id] = revision; + out.push_back(tmp); + } + } + } + + setContent(req, res, out.dump()); + }); + + s.Get("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1]; + auto memberID = req.matches[2]; + uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str()); + uint64_t memid = Utils::hexStrToU64(memberID.str().c_str()); + json network; + json member; + if (!_db.get(nwid, network, memid, member)) { + res.status = 404; + return; + } + + setContent(req, res, member.dump()); + }); + + auto memberPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1].str(); + auto memberID = req.matches[2].str(); + uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); + uint64_t memid = Utils::hexStrToU64(memberID.c_str()); + json network; + json member; + _db.get(nwid, network, memid, member); + DB::initMember(member); + + json b = OSUtils::jsonParse(req.body); + + if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"], false); + if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"], false); + if (b.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = (uint64_t)OSUtils::jsonInt(b["authenticationExpiryTime"], 0ULL); + if (b.count("authenticationURL")) member["authenticationURL"] = OSUtils::jsonString(b["authenticationURL"], ""); + + if (b.count("remoteTraceTarget")) { + const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],"")); + if (rtt.length() == 10) { + member["remoteTraceTarget"] = rtt; + } else { + member["remoteTraceTarget"] = json(); + } + } + if (b.count("remoteTraceLevel")) member["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL); + + if (b.count("authorized")) { + const bool newAuth = OSUtils::jsonBool(b["authorized"],false); + if (newAuth != OSUtils::jsonBool(member["authorized"],false)) { + member["authorized"] = newAuth; + member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = OSUtils::now(); + if (newAuth) { + member["lastAuthorizedCredentialType"] = "api"; + member["lastAuthorizedCredential"] = json(); + } + } + } + + if (b.count("ipAssignments")) { + json &ipa = b["ipAssignments"]; + if (ipa.is_array()) { + json mipa(json::array()); + for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + } + member["ipAssignments"] = mipa; + } + } + + if (b.count("tags")) { + json &tags = b["tags"]; + if (tags.is_array()) { + std::map mtags; + for(unsigned long i=0;i::iterator t(mtags.begin());t!=mtags.end();++t) { + json ta = json::array(); + ta.push_back(t->first); + ta.push_back(t->second); + mtagsa.push_back(ta); + if (mtagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + member["tags"] = mtagsa; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + json mcaps = json::array(); + for(unsigned long i=0;i= ZT_CONTROLLER_MAX_ARRAY_SIZE) + break; + } + std::sort(mcaps.begin(),mcaps.end()); + mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end()); + member["capabilities"] = mcaps; + } + } + + member["id"] = memberID; + member["address"] = memberID; + member["nwid"] = networkID; + + DB::cleanMember(member); + _db.save(member, true); + + setContent(req, res, member.dump()); + }; + s.Put("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost); + s.Post("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost); + + s.Delete("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto networkID = req.matches[1].str(); + auto memberID = req.matches[2].str(); + + uint64_t nwid = Utils::hexStrToU64(networkID.c_str()); + uint64_t address = Utils::hexStrToU64(memberID.c_str()); + json network, member; + + if (!_db.get(nwid, network, address, member)) { + res.status = 404; + return; + } + + if (!member.size()) { + res.status = 404; + return; + } + + _db.eraseMember(nwid, address); + + setContent(req, res, member.dump()); + }); } void EmbeddedNetworkController::handleRemoteTrace(const ZT_RemoteTrace &rt) @@ -1235,36 +1222,66 @@ void EmbeddedNetworkController::_request( const Identity &identity, const Dictionary &metaData) { + Metrics::network_config_request++; + auto tid = std::this_thread::get_id(); + std::stringstream ss; ss << tid; + std::string threadID = ss.str(); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b1 = _member_status_lookup.Add({{"thread", threadID}}); + auto c1 = _member_status_lookup_count.Add({{"thread", threadID}}); + c1++; + b1.start(); +#endif + char nwids[24]; DB::NetworkSummaryInfo ns; json network,member; - if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) { return; + } const int64_t now = OSUtils::now(); - if (requestPacketId) { - std::lock_guard l(_memberStatus_l); - _MemberStatus &ms = _memberStatus[_MemberStatusKey(nwid,identity.address().toInt())]; - if ((now - ms.lastRequestTime) <= ZT_NETCONF_MIN_REQUEST_PERIOD) - return; - ms.lastRequestTime = now; - } - +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b1.stop(); + auto b2 = _node_is_online.Add({{"thread",threadID}}); + auto c2 = _node_is_online_count.Add({{"thread",threadID}}); + c2++; + b2.start(); +#endif _db.nodeIsOnline(nwid,identity.address().toInt(),fromAddr); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b2.stop(); + auto b3 = _get_and_init_member.Add({{"thread", threadID}}); + auto c3 = _get_and_init_member_count.Add({{"thread",threadID}}); + c3++; + b3.start(); +#endif Utils::hex(nwid,nwids); _db.get(nwid,network,identity.address().toInt(),member,ns); if ((!network.is_object())||(network.empty())) { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND, nullptr, 0); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b3.stop(); +#endif return; } const bool newMember = ((!member.is_object())||(member.empty())); DB::initMember(member); _MemberStatusKey msk(nwid,identity.address().toInt()); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b3.stop(); +#endif { +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b4 = _have_identity.Add({{"thread",threadID}}); + auto c4 = _have_identity_count.Add({{"thread",threadID}}); + c4++; + b4.start(); +#endif const std::string haveIdStr(OSUtils::jsonString(member["identity"],"")); if (haveIdStr.length() > 0) { // If we already know this member's identity perform a full compare. This prevents @@ -1273,10 +1290,16 @@ void EmbeddedNetworkController::_request( try { if (Identity(haveIdStr.c_str()) != identity) { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b4.stop(); + #endif return; } } catch ( ... ) { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b4.stop(); + #endif return; } } else { @@ -1284,6 +1307,9 @@ void EmbeddedNetworkController::_request( char idtmp[1024]; member["identity"] = identity.toString(false,idtmp); } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b4.stop(); +#endif } // These are always the same, but make sure they are set @@ -1296,6 +1322,12 @@ void EmbeddedNetworkController::_request( } // Determine whether and how member is authorized +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b5 = _determine_auth.Add({{"thread",threadID}}); + auto c5 = _determine_auth_count.Add({{"thread",threadID}}); + c5++; + b5.start(); +#endif bool authorized = false; bool autoAuthorized = false; json autoAuthCredentialType,autoAuthCredential; @@ -1332,10 +1364,19 @@ void EmbeddedNetworkController::_request( member["lastAuthorizedCredentialType"] = autoAuthCredentialType; member["lastAuthorizedCredential"] = autoAuthCredential; } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b5.stop(); +#endif // Should we check SSO Stuff? // If network is configured with SSO, and the member is not marked exempt: yes // Otherwise no, we use standard auth logic. +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b6 = _sso_check.Add({{"thread",threadID}}); + auto c6 = _sso_check_count.Add({{"thread",threadID}}); + c6++; + b6.start(); +#endif AuthInfo info; int64_t authenticationExpiryTime = -1; bool networkSSOEnabled = OSUtils::jsonBool(network["ssoEnabled"], false); @@ -1362,10 +1403,20 @@ void EmbeddedNetworkController::_request( } DB::cleanMember(member); _db.save(member,true); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b6.stop(); + #endif return; } } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b6.stop(); + auto b7 = _auth_check.Add({{"thread",threadID}}); + auto c7 = _auth_check_count.Add({{"thread",threadID}}); + c7++; + b7.start(); +#endif if (authorized) { // Update version info and meta-data if authorized and if this is a genuine request if (requestPacketId) { @@ -1401,8 +1452,14 @@ void EmbeddedNetworkController::_request( DB::cleanMember(member); _db.save(member,true); _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED, nullptr, 0); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b7.stop(); + #endif return; } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b7.stop(); +#endif // ------------------------------------------------------------------------- // If we made it this far, they are authorized (and authenticated). @@ -1410,6 +1467,12 @@ void EmbeddedNetworkController::_request( // Default timeout: 15 minutes. Maximum: two hours. Can be specified by an optional field in the network config // if something longer than 15 minutes is desired. Minimum is 5 minutes since shorter than that would be flaky. +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b8 = _json_schlep.Add({{"thread",threadID}}); + auto c8 = _json_schlep_count.Add({{"thread", threadID}}); + c8++; + b8.start(); +#endif int64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_DFL_MAX_DELTA; if (network.contains("certificateTimeoutWindowSize")) { credentialtmd = (int64_t)network["certificateTimeoutWindowSize"]; @@ -1477,8 +1540,9 @@ void EmbeddedNetworkController::_request( nc->remoteTraceLevel = (Trace::Level)OSUtils::jsonInt(network["remoteTraceLevel"],0ULL); } - for(std::vector
::const_iterator ab(ns.activeBridges.begin());ab!=ns.activeBridges.end();++ab) + for(std::vector
::const_iterator ab(ns.activeBridges.begin());ab!=ns.activeBridges.end();++ab) { nc->addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); + } json &v4AssignMode = network["v4AssignMode"]; json &v6AssignMode = network["v6AssignMode"]; @@ -1607,7 +1671,7 @@ void EmbeddedNetworkController::_request( *(reinterpret_cast(&(r->target))) = t; if (v.ss_family == t.ss_family) *(reinterpret_cast(&(r->via))) = v; - ++nc->routeCount; + ++nc->routeCount; } } } @@ -1798,12 +1862,22 @@ void EmbeddedNetworkController::_request( } else { dns = json::object(); } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b8.stop(); +#endif // Issue a certificate of ownership for all static IPs +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + auto b9 = _issue_certificate.Add({{"thread",threadID}}); + auto c9 = _issue_certificate_count.Add({{"thread",threadID}}); + c9++; + b9.start(); +#endif if (nc->staticIpCount) { nc->certificatesOfOwnership[0] = CertificateOfOwnership(nwid,now,identity.address(),1); - for(unsigned int i=0;istaticIpCount;++i) + for(unsigned int i=0;istaticIpCount;++i) { nc->certificatesOfOwnership[0].addThing(nc->staticIps[i]); + } nc->certificatesOfOwnership[0].sign(_signingId); nc->certificateOfOwnershipCount = 1; } @@ -1813,26 +1887,48 @@ void EmbeddedNetworkController::_request( nc->com = com; } else { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR, nullptr, 0); + #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b9.stop(); + #endif return; } +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b9.stop(); + auto b10 = _save_member.Add({{"thread",threadID}}); + auto c10 = _save_member_count.Add({{"thread",threadID}}); + c10++; + b10.start(); +#endif DB::cleanMember(member); _db.save(member,true); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b10.stop(); + + auto b11 = _send_netconf.Add({{"thread",threadID}}); + auto c11 = _send_netconf_count.Add({{"thread",threadID}}); + c11++; + b11.start(); +#endif _sender->ncSendConfig(nwid,requestPacketId,identity.address(),*(nc.get()),metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6); +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + b11.stop(); +#endif } void EmbeddedNetworkController::_startThreads() { std::lock_guard l(_threads_l); - if (!_threads.empty()) + if (!_threads.empty()) { return; + } const long hwc = std::max((long)std::thread::hardware_concurrency(),(long)1); for(long t=0;t expired; - nlohmann::json network, member; + Metrics::network_config_request_threads++; for(;;) { _RQEntry *qe = (_RQEntry *)0; + Metrics::network_config_request_queue_size = _queue.size(); auto timedWaitResult = _queue.get(qe, 1000); if (timedWaitResult == BlockingQueue<_RQEntry *>::STOP) { break; @@ -1846,38 +1942,48 @@ void EmbeddedNetworkController::_startThreads() fprintf(stderr,"ERROR: exception in controller request handling thread: unknown exception" ZT_EOL_S); } delete qe; + qe = nullptr; } } - - expired.clear(); - int64_t now = OSUtils::now(); - { - std::lock_guard l(_expiringSoon_l); - for(auto s=_expiringSoon.begin();s!=_expiringSoon.end();) { - const int64_t when = s->first; - if (when <= now) { - // The user may have re-authorized, so we must actually look it up and check. - network.clear(); - member.clear(); - if (_db.get(s->second.networkId, network, s->second.nodeId, member)) { - int64_t authenticationExpiryTime = (int64_t)OSUtils::jsonInt(member["authenticationExpiryTime"], 0); - if (authenticationExpiryTime <= now) { - expired.push_back(s->second); - } - } - _expiringSoon.erase(s++); - } else { - // Don't bother going further into the future than necessary. - break; - } - } - } - for(auto e=expired.begin();e!=expired.end();++e) { - onNetworkMemberDeauthorize(nullptr, e->networkId, e->nodeId); - } } + Metrics::network_config_request_threads--; }); } } +void EmbeddedNetworkController::_ssoExpiryThread() { + while(_ssoExpiryRunning) { + std::vector<_MemberStatusKey> expired; + nlohmann::json network, member; + int64_t now = OSUtils::now(); + { + std::lock_guard l(_expiringSoon_l); + for(auto s=_expiringSoon.begin();s!=_expiringSoon.end();) { + Metrics::sso_expiration_checks++; + const int64_t when = s->first; + if (when <= now) { + // The user may have re-authorized, so we must actually look it up and check. + network.clear(); + member.clear(); + if (_db.get(s->second.networkId, network, s->second.nodeId, member)) { + int64_t authenticationExpiryTime = (int64_t)OSUtils::jsonInt(member["authenticationExpiryTime"], 0); + if (authenticationExpiryTime <= now) { + expired.push_back(s->second); + } + } + s = _expiringSoon.erase(s); + } else { + // Don't bother going further into the future than necessary. + break; + } + } + } + for(auto e=expired.begin();e!=expired.end();++e) { + Metrics::sso_member_deauth++; + onNetworkMemberDeauthorize(nullptr, e->networkId, e->nodeId); + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } +} + } // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index bc95acb58..ef369be39 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -37,6 +37,8 @@ #include +#include + #include "DB.hpp" #include "DBMirrorSet.hpp" @@ -66,27 +68,9 @@ public: const Identity &identity, const Dictionary &metaData); - unsigned int handleControlPlaneHttpGET( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType); - unsigned int handleControlPlaneHttpPOST( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType); - unsigned int handleControlPlaneHttpDELETE( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType); + void configureHTTPControlPlane( + httplib::Server &s, + const std::function); void handleRemoteTrace(const ZT_RemoteTrace &rt); @@ -97,6 +81,9 @@ public: private: void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary &metaData); void _startThreads(); + void _ssoExpiryThread(); + + std::string networkUpdateFromPostData(uint64_t networkID, const std::string &body); struct _RQEntry { @@ -160,6 +147,34 @@ private: RedisConfig *_rc; std::string _ssoRedirectURL; + + bool _ssoExpiryRunning; + std::thread _ssoExpiry; + +#ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK + prometheus::simpleapi::benchmark_family_t _member_status_lookup; + prometheus::simpleapi::counter_family_t _member_status_lookup_count; + prometheus::simpleapi::benchmark_family_t _node_is_online; + prometheus::simpleapi::counter_family_t _node_is_online_count; + prometheus::simpleapi::benchmark_family_t _get_and_init_member; + prometheus::simpleapi::counter_family_t _get_and_init_member_count; + prometheus::simpleapi::benchmark_family_t _have_identity; + prometheus::simpleapi::counter_family_t _have_identity_count; + prometheus::simpleapi::benchmark_family_t _determine_auth; + prometheus::simpleapi::counter_family_t _determine_auth_count; + prometheus::simpleapi::benchmark_family_t _sso_check; + prometheus::simpleapi::counter_family_t _sso_check_count; + prometheus::simpleapi::benchmark_family_t _auth_check; + prometheus::simpleapi::counter_family_t _auth_check_count; + prometheus::simpleapi::benchmark_family_t _json_schlep; + prometheus::simpleapi::counter_family_t _json_schlep_count; + prometheus::simpleapi::benchmark_family_t _issue_certificate; + prometheus::simpleapi::counter_family_t _issue_certificate_count; + prometheus::simpleapi::benchmark_family_t _save_member; + prometheus::simpleapi::counter_family_t _save_member_count; + prometheus::simpleapi::benchmark_family_t _send_netconf; + prometheus::simpleapi::counter_family_t _send_netconf_count; +#endif }; } // namespace ZeroTier diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 00b61bc35..319c02684 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -374,6 +374,7 @@ void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) { + Metrics::db_get_sso_info++; // NONCE is just a random character string. no semantic meaning // state = HMAC SHA384 of Nonce based on shared sso key // @@ -388,9 +389,16 @@ AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::str char authenticationURL[4096] = {0}; AuthInfo info; info.enabled = true; + + //if (memberId == "a10dccea52" && networkId == "8056c2e21c24673d") { + // fprintf(stderr, "invalid authinfo for grant's machine\n"); + // info.version=1; + // return info; + //} // fprintf(stderr, "PostgreSQL::updateMemberOnLoad: %s-%s\n", networkId.c_str(), memberId.c_str()); + std::shared_ptr c; try { - auto c = _pool->borrow(); + c = _pool->borrow(); pqxx::work w(*c->c); char nonceBytes[16] = {0}; @@ -452,7 +460,7 @@ AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::str "LEFT OUTER JOIN ztc_network_oidc_config noc " " ON noc.network_id = n.id " "LEFT OUTER JOIN ztc_oidc_config oc " - " ON noc.client_id = oc.client_id AND noc.org_id = o.org_id " + " ON noc.client_id = oc.client_id AND oc.org_id = o.org_id " "WHERE n.id = $1 AND n.sso_enabled = true", networkId); std::string client_id = ""; @@ -462,11 +470,11 @@ AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::str uint64_t sso_version = 0; if (r.size() == 1) { - client_id = r.at(0)[0].as(); - authorization_endpoint = r.at(0)[1].as(); - issuer = r.at(0)[2].as(); - provider = r.at(0)[3].as(); - sso_version = r.at(0)[4].as(); + client_id = r.at(0)[0].as>().value_or(""); + authorization_endpoint = r.at(0)[1].as>().value_or(""); + issuer = r.at(0)[2].as>().value_or(""); + provider = r.at(0)[3].as>().value_or(""); + sso_version = r.at(0)[4].as>().value_or(1); } else if (r.size() > 1) { fprintf(stderr, "ERROR: More than one auth endpoint for an organization?!?!? NetworkID: %s\n", networkId.c_str()); } else { @@ -776,7 +784,7 @@ void PostgreSQL::initializeMembers() if (_redisMemberStatus) { fprintf(stderr, "Initialize Redis for members...\n"); - std::lock_guard l(_networks_l); + std::unique_lock l(_networks_l); std::unordered_set deletes; for ( auto it : _networks) { uint64_t nwid_i = it.first; @@ -1040,7 +1048,6 @@ void PostgreSQL::heartbeat() w.commit(); } catch (std::exception &e) { fprintf(stderr, "%s: Heartbeat update failed: %s\n", controllerId, e.what()); - _pool->unborrow(c); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); continue; } @@ -1270,6 +1277,7 @@ void PostgreSQL::commitThread() continue; } + Metrics::pgsql_commit_ticks++; try { nlohmann::json &config = (qitem.first); const std::string objtype = config["objtype"]; @@ -1596,7 +1604,6 @@ void PostgreSQL::commitThread() } _pool->unborrow(c); c.reset(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); } fprintf(stderr, "%s commitThread finished\n", _myAddressStr.c_str()); @@ -1812,7 +1819,7 @@ uint64_t PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &con sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); { - std::lock_guard l(_networks_l); + std::shared_lock l(_networks_l); for (const auto &it : _networks) { uint64_t nwid_i = it.first; char nwidTmp[64]; diff --git a/controller/README.md b/controller/README.md index 331710a15..41cfd3ff3 100644 --- a/controller/README.md +++ b/controller/README.md @@ -3,7 +3,7 @@ Network Controller Microservice Every ZeroTier virtual network has a *network controller* responsible for admitting members to the network, issuing certificates, and issuing default configuration information. -This is our reference controller implementation and is the same one we use to power our own hosted services at [my.zerotier.com](https://my.zerotier.com/). As of ZeroTier One version 1.2.0 this code is included in normal builds for desktop, laptop, and server (Linux, etc.) targets. +This is our reference controller implementation and is almost the same as the one we use to power our own hosted services at [my.zerotier.com](https://my.zerotier.com/). The only difference is the database backend used. Controller data is stored in JSON format under `controller.d` in the ZeroTier working directory. It can be copied, rsync'd, placed in `git`, etc. The files under `controller.d` should not be modified in place while the controller is running or data loss may result, and if they are edited directly take care not to save corrupt JSON since that can also lead to data loss when the controller is restarted. Going through the API is strongly preferred to directly modifying these files. @@ -19,10 +19,6 @@ Since ZeroTier nodes are mobile and do not need static IPs, implementing high av ZeroTier network controllers can easily be run in Docker or other container systems. Since containers do not need to actually join networks, extra privilege options like "--device=/dev/net/tun --privileged" are not needed. You'll just need to map the local JSON API port of the running controller and allow it to access the Internet (over UDP/9993 at a minimum) so things can reach and query it. -### PostgreSQL Database Implementation - -The default controller stores its data in the filesystem in `controller.d` under ZeroTier's home folder. There's an alternative implementation that stores data in PostgreSQL that can be built with `make central-controller`. Right now this is only guaranteed to build and run on Centos 7 Linux with PostgreSQL 10 installed via the [PostgreSQL Yum Repository](https://www.postgresql.org/download/linux/redhat/) and is designed for use with [ZeroTier Central](https://my.zerotier.com/). You're welcome to use it but we don't "officially" support it for end-user use and it could change at any time. - ### Upgrading from Older (1.1.14 or earlier) Versions Older versions of this code used a SQLite database instead of in-filesystem JSON. A migration utility called `migrate-sqlite` is included here and *must* be used to migrate this data to the new format. If the controller is started with an old `controller.db` in its working directory it will terminate after printing an error to *stderr*. This is done to prevent "surprises" for those running DIY controllers using the old code. @@ -43,210 +39,17 @@ While networks with any valid ID can be added to the controller's database, it w The controller JSON API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrect types may be ignored, set to default values, or set to undefined values. -#### `/controller` +Full documentation of the Controller API can be found on our [documentation site](https://docs.zerotier.com/service/v1#tag/controller) - * Purpose: Check for controller function and return controller status - * Methods: GET - * Returns: { object } +### Prometheus Metrics -| Field | Type | Description | Writable | -| ------------------ | ----------- | ------------------------------------------------- | -------- | -| controller | boolean | Always 'true' | no | -| apiVersion | integer | Controller API version, currently 3 | no | -| clock | integer | Current clock on controller, ms since epoch | no | - -#### `/controller/network` - - * Purpose: List all networks hosted by this controller - * Methods: GET - * Returns: [ string, ... ] - -This returns an array of 16-digit hexadecimal network IDs. - -#### `/controller/network/` - - * Purpose: Create, configure, and delete hosted networks - * Methods: GET, POST, DELETE - * Returns: { object } - -By making queries to this path you can create, configure, and delete networks. DELETE is final, so don't do it unless you really mean it. - -When POSTing new networks take care that their IDs are not in use, otherwise you may overwrite an existing one. To create a new network with a random unused ID, POST to `/controller/network/##########______`. The #'s are the controller's 10-digit ZeroTier address and they're followed by six underscores. Check the `nwid` field of the returned JSON object for your network's newly allocated ID. Subsequent POSTs to this network must refer to its actual path. - -Example: - -`curl -X POST --header "X-ZT1-Auth: secret" -d '{"name":"my network"}' http://localhost:9993/controller/network/305f406058______` - -**Network object format:** - -| Field | Type | Description | Writable | -| --------------------- | ------------- | ------------------------------------------------- | -------- | -| id | string | 16-digit network ID | no | -| nwid | string | 16-digit network ID (legacy) | no | -| objtype | string | Always "network" | no | -| name | string | A short name for this network | YES | -| creationTime | integer | Time network record was created (ms since epoch) | no | -| private | boolean | Is access control enabled? | YES | -| enableBroadcast | boolean | Ethernet ff:ff:ff:ff:ff:ff allowed? | YES | -| v4AssignMode | object | IPv4 management and assign options (see below) | YES | -| v6AssignMode | object | IPv6 management and assign options (see below) | YES | -| mtu | integer | Network MTU (default: 2800) | YES | -| multicastLimit | integer | Maximum recipients for a multicast packet | YES | -| revision | integer | Network config revision counter | no | -| routes | array[object] | Managed IPv4 and IPv6 routes; see below | YES | -| ipAssignmentPools | array[object] | IP auto-assign ranges; see below | YES | -| rules | array[object] | Traffic rules; see below | YES | -| capabilities | array[object] | Array of capability objects (see below) | YES | -| tags | array[object] | Array of tag objects (see below) | YES | -| remoteTraceTarget | string | 10-digit ZeroTier ID of remote trace target | YES | -| remoteTraceLevel | integer | Remote trace verbosity level | YES | - - * Networks without rules won't carry any traffic. If you don't specify any on network creation an "accept anything" rule set will automatically be added. - * Managed IP address assignments and IP assignment pools that do not fall within a route configured in `routes` are ignored and won't be used or sent to members. - * The default for `private` is `true` and this is probably what you want. Turning `private` off means *anyone* can join your network with only its 16-digit network ID. It's also impossible to de-authorize a member as these networks don't issue or enforce certificates. Such "party line" networks are used for decentralized app backplanes, gaming, and testing but are otherwise not common. - * Changing the MTU can be disruptive and on some operating systems may require a leave/rejoin of the network or a restart of the ZeroTier service. - -**Auto-Assign Modes:** - -Auto assign modes (`v4AssignMode` and `v6AssignMode`) contain objects that map assignment modes to booleans. - -For IPv4 the only valid setting is `zt` which, if true, causes IPv4 addresses to be auto-assigned from `ipAssignmentPools` to members that do not have an IPv4 assignment. Note that active bridges are exempt and will not get auto-assigned IPs since this can interfere with bridging. (You can still manually assign one if you want.) - -IPv6 includes this option and two others: `6plane` and `rfc4193`. These assign private IPv6 addresses to each member based on a deterministic assignment scheme that allows members to emulate IPv6 NDP to skip multicast for better performance and scalability. The `rfc4193` mode gives every member a /128 on a /88 network, while `6plane` gives every member a /80 within a /40 network but uses NDP emulation to route *all* IPs under that /80 to its owner. The `6plane` mode is great for use cases like Docker since it allows every member to assign IPv6 addresses within its /80 that just work instantly and globally across the network. - -**IP assignment pool object format:** - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| ipRangeStart | string | Starting IP address in range | -| ipRangeEnd | string | Ending IP address in range (inclusive) | - -Pools are only used if auto-assignment is on for the given address type (IPv4 or IPv6) and if the entire range falls within a managed route. - -IPv6 ranges work just like IPv4 ranges and look like this: - - { - "ipRangeStart": "fd00:feed:feed:beef:0000:0000:0000:0000", - "ipRangeEnd": "fd00:feed:feed:beef:ffff:ffff:ffff:ffff" - } - -(You can POST a shortened-form IPv6 address but the API will always report back un-shortened canonical form addresses.) - -That defines a range within network `fd00:feed:feed:beef::/64` that contains up to 2^64 addresses. If an IPv6 range is large enough, the controller will assign addresses by placing each member's device ID into the address in a manner similar to the RFC4193 and 6PLANE modes. Otherwise it will assign addresses at random. - -**Managed Route object format:** - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| target | string | Subnet in CIDR notation | -| via | string/null | Next hop router IP address | - -Managed Route objects look like this: - - { - "target": "10.147.20.0/24" - } - -or - - { - "target": "192.168.168.0/24", - "via": "10.147.20.1" - } - -**Rule object format:** - -Each rule is actually a sequence of zero or more `MATCH_` entries in the rule array followed by an `ACTION_` entry that describes what to do if all the preceding entries match. An `ACTION_` without any preceding `MATCH_` entries is always taken, so setting a single `ACTION_ACCEPT` rule yields a network that allows all traffic. If no rules are present the default action is `ACTION_DROP`. - -Rules are evaluated in the order in which they appear in the array. There is currently a limit of 256 entries per network. Capabilities should be used if a larger and more complex rule set is needed since they allow rules to be grouped by purpose and only shipped to members that need them. - -Each rule table entry has two common fields. - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| type | string | Entry type (all caps, case sensitive) | -| not | boolean | If true, MATCHes match if they don't match | - -The following fields may or may not be present depending on rule type: - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| zt | string | 10-digit hex ZeroTier address | -| etherType | integer | Ethernet frame type | -| mac | string | Hex MAC address (with or without :'s) | -| ip | string | IPv4 or IPv6 address | -| ipTos | integer | IP type of service | -| ipProtocol | integer | IP protocol (e.g. TCP) | -| start | integer | Start of an integer range (e.g. port range) | -| end | integer | End of an integer range (inclusive) | -| id | integer | Tag ID | -| value | integer | Tag value or comparison value | -| mask | integer | Bit mask (for characteristics flags) | - -The entry types and their additional fields are: - -| Entry type | Description | Fields | -| ------------------------------- | ----------------------------------------------------------------- | -------------- | -| `ACTION_DROP` | Drop any packets matching this rule | (none) | -| `ACTION_ACCEPT` | Accept any packets matching this rule | (none) | -| `ACTION_TEE` | Send a copy of this packet to a node (rule parsing continues) | `zt` | -| `ACTION_REDIRECT` | Redirect this packet to another node | `zt` | -| `ACTION_DEBUG_LOG` | Output debug info on match (if built with rules engine debug) | (none) | -| `MATCH_SOURCE_ZEROTIER_ADDRESS` | Match VL1 ZeroTier address of packet sender. | `zt` | -| `MATCH_DEST_ZEROTIER_ADDRESS` | Match VL1 ZeroTier address of recipient | `zt` | -| `MATCH_ETHERTYPE` | Match Ethernet frame type | `etherType` | -| `MATCH_MAC_SOURCE` | Match source Ethernet MAC address | `mac` | -| `MATCH_MAC_DEST` | Match destination Ethernet MAC address | `mac` | -| `MATCH_IPV4_SOURCE` | Match source IPv4 address | `ip` | -| `MATCH_IPV4_DEST` | Match destination IPv4 address | `ip` | -| `MATCH_IPV6_SOURCE` | Match source IPv6 address | `ip` | -| `MATCH_IPV6_DEST` | Match destination IPv6 address | `ip` | -| `MATCH_IP_TOS` | Match IP TOS field | `ipTos` | -| `MATCH_IP_PROTOCOL` | Match IP protocol field | `ipProtocol` | -| `MATCH_IP_SOURCE_PORT_RANGE` | Match a source IP port range | `start`,`end` | -| `MATCH_IP_DEST_PORT_RANGE` | Match a destination IP port range | `start`,`end` | -| `MATCH_CHARACTERISTICS` | Match on characteristics flags | `mask`,`value` | -| `MATCH_FRAME_SIZE_RANGE` | Match a range of Ethernet frame sizes | `start`,`end` | -| `MATCH_TAGS_SAMENESS` | Match if both sides' tags differ by no more than value | `id`,`value` | -| `MATCH_TAGS_BITWISE_AND` | Match if both sides' tags AND to value | `id`,`value` | -| `MATCH_TAGS_BITWISE_OR` | Match if both sides' tags OR to value | `id`,`value` | -| `MATCH_TAGS_BITWISE_XOR` | Match if both sides' tags XOR to value | `id`,`value` | - -Important notes about rules engine behavior: - - * IPv4 and IPv6 IP address rules do not match for frames that are not IPv4 or IPv6 respectively. - * `ACTION_DEBUG_LOG` is a no-op on nodes not built with `ZT_RULES_ENGINE_DEBUGGING` enabled (see Network.cpp). If that is enabled nodes will dump a trace of rule evaluation results to *stdout* when this action is encountered but will otherwise keep evaluating rules. This is used for basic "smoke testing" of the rules engine. - * Multicast packets and packets destined for bridged devices treated a little differently. They are matched more than once. They are matched at the point of send with a NULL ZeroTier destination address, meaning that `MATCH_DEST_ZEROTIER_ADDRESS` is useless. That's because the true VL1 destination is not yet known. Then they are matched again for each true VL1 destination. On these later subsequent matches TEE actions are ignored and REDIRECT rules are interpreted as DROPs. This prevents multiple TEE or REDIRECT packets from being sent to third party devices. - * Rules in capabilities are always matched as if the current device is the sender (inbound == false). A capability specifies sender side rules that can be enforced on both sides. - -#### `/controller/network//member` - - * Purpose: Get a set of all members on this network - * Methods: GET - * Returns: { object } - -This returns a JSON object containing all member IDs as keys and their `memberRevisionCounter` values as values. - -#### `/controller/network//member/
` - - * Purpose: Create, authorize, or remove a network member - * Methods: GET, POST, DELETE - * Returns: { object } - -| Field | Type | Description | Writable | -| --------------------- | ------------- | ------------------------------------------------- | -------- | -| id | string | Member's 10-digit ZeroTier address | no | -| address | string | Member's 10-digit ZeroTier address | no | -| nwid | string | 16-digit network ID | no | -| authorized | boolean | Is member authorized? (for private networks) | YES | -| activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES | -| identity | string | Member's public ZeroTier identity (if known) | no | -| ipAssignments | array[string] | Managed IP address assignments | YES | -| revision | integer | Member revision counter | no | -| vMajor | integer | Most recently known major version | no | -| vMinor | integer | Most recently known minor version | no | -| vRev | integer | Most recently known revision | no | -| vProto | integer | Most recently known protocol version | no | - -Note that managed IP assignments are only used if they fall within a managed route. Otherwise they are ignored. +Controller specific metrics are available from the `/metrics` endpoint. +| Metric Name | Type | Description | +| --- | --- | --- | +| controller_network_count | Gauge | number of networks the controller is serving | +| controller_member_count | Gauge | number of network members the controller is serving | +| controller_network_change_count | Counter | number of times a network configuration is changed | +| controller_member_change_count | Counter | number of times a network member configuration is changed | +| controller_member_auth_count | Counter | number of network member auths | +| controller_member_deauth_count | Counter | number of network member deauths| diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index c59aa8f5d..c134cbdcf 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -1,10 +1,10 @@ # Dockerfile for ZeroTier Central Controllers -FROM registry.zerotier.com/zerotier/controller-builder:latest as builder +FROM registry.zerotier.com/zerotier/ctlbuild:latest as builder MAINTAINER Adam Ierymekno , Grant Limberg ADD . /ZeroTierOne RUN export PATH=$PATH:~/.cargo/bin && cd ZeroTierOne && make clean && make central-controller -j8 -FROM registry.zerotier.com/zerotier/controller-run:latest +FROM registry.zerotier.com/zerotier/ctlrun:latest COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one RUN chmod a+x /usr/local/bin/zerotier-one RUN echo "/usr/local/lib64" > /etc/ld.so.conf.d/usr-local-lib64.conf && ldconfig diff --git a/ext/central-controller-docker/Makefile b/ext/central-controller-docker/Makefile new file mode 100644 index 000000000..46ec8ee16 --- /dev/null +++ b/ext/central-controller-docker/Makefile @@ -0,0 +1,16 @@ +registry = registry.zerotier.com/zerotier + +all: controller-builder controller-runbase + +buildx: + @echo "docker buildx create" + # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + docker run --privileged --rm tonistiigi/binfmt --install all + @echo docker buildx create --name multiarch --driver docker-container --use + @echo docker buildx inspect --bootstrap + +controller-builder: buildx + docker buildx build --no-cache --platform linux/amd64,linux/arm64 -t $(registry)/ctlbuild:latest -f Dockerfile.builder . --push + +controller-runbase: buildx + docker buildx build --no-cache --platform linux/amd64,linux/arm64 -t $(registry)/ctlrun:latest -f Dockerfile.run_base . --push diff --git a/ext/central-controller-docker/main.sh b/ext/central-controller-docker/main.sh index a258c0902..1ec8e10f2 100755 --- a/ext/central-controller-docker/main.sh +++ b/ext/central-controller-docker/main.sh @@ -64,6 +64,7 @@ fi popd DEFAULT_PORT=9993 +DEFAULT_LB_MODE=false APP_NAME="controller-$(cat /var/lib/zerotier-one/identity.public | cut -d ':' -f 1)" @@ -76,6 +77,7 @@ echo "{ \"inot\", \"nat64\" ], + \"lowBandwidthMode\": ${ZT_LB_MODE:-$DEFAULT_LB_MODE}, \"ssoRedirectURL\": \"${ZT_SSO_REDIRECT_URL}\", \"allowManagementFrom\": [\"127.0.0.1\", \"::1\", \"10.0.0.0/8\"], ${REDIS} diff --git a/ext/cpp-httplib/httplib.h b/ext/cpp-httplib/httplib.h index 3947df060..28746000c 100644 --- a/ext/cpp-httplib/httplib.h +++ b/ext/cpp-httplib/httplib.h @@ -1,13 +1,15 @@ // // httplib.h // -// Copyright (c) 2020 Yuji Hirose. All rights reserved. +// Copyright (c) 2023 Yuji Hirose. All rights reserved. // MIT License // #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_VERSION "0.12.2" + /* * Configuration */ @@ -60,14 +62,26 @@ #define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 #endif +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 #endif +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + #ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH #define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) #endif +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + #ifndef CPPHTTPLIB_TCP_NODELAY #define CPPHTTPLIB_TCP_NODELAY false #endif @@ -87,6 +101,18 @@ : 0)) #endif +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + /* * Headers */ @@ -101,14 +127,16 @@ #endif //_CRT_NONSTDC_NO_DEPRECATE #if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + #ifdef _WIN64 using ssize_t = __int64; #else -using ssize_t = int; -#endif - -#if _MSC_VER < 1900 -#define snprintf _snprintf_s +using ssize_t = long; #endif #endif // _MSC_VER @@ -126,20 +154,12 @@ using ssize_t = int; #include #include - -#include #include #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif -#ifdef _MSC_VER -#pragma comment(lib, "ws2_32.lib") -#pragma comment(lib, "crypt32.lib") -#pragma comment(lib, "cryptui.lib") -#endif - #ifndef strcasecmp #define strcasecmp _stricmp #endif // strcasecmp @@ -152,8 +172,10 @@ using socket_t = SOCKET; #else // not _WIN32 #include -#include +#ifndef _AIX #include +#endif +#include #include #include #ifdef __linux__ @@ -167,22 +189,28 @@ using socket_t = SOCKET; #include #include #include +#include #include using socket_t = int; +#ifndef INVALID_SOCKET #define INVALID_SOCKET (-1) +#endif #endif //_WIN32 +#include #include #include #include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -190,14 +218,37 @@ using socket_t = int; #include #include #include +#include #include #include #include #include #ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + #include -#include +#include #include #include @@ -205,20 +256,15 @@ using socket_t = int; #include #endif -#include #include #include #if OPENSSL_VERSION_NUMBER < 0x1010100fL #error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#define SSL_get1_peer_certificate SSL_get_peer_certificate #endif -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#include -inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) { - return M_ASN1_STRING_data(asn1); -} -#endif #endif #ifdef CPPHTTPLIB_ZLIB_SUPPORT @@ -237,14 +283,65 @@ namespace httplib { namespace detail { +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + struct ci { bool operator()(const std::string &s1, const std::string &s2) const { - return std::lexicographical_compare( - s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); }); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); } }; +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + } // namespace detail using Headers = std::multimap; @@ -275,9 +372,9 @@ public: DataSink(DataSink &&) = delete; DataSink &operator=(DataSink &&) = delete; - std::function write; + std::function write; std::function done; - std::function is_writable; + std::function done_with_trailer; std::ostream os; private: @@ -304,6 +401,20 @@ using ContentProvider = using ContentProviderWithoutLength = std::function; +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + using ContentReceiver = std::function; @@ -317,14 +428,17 @@ public: ContentReceiver receiver)>; ContentReader(Reader reader, MultipartReader multipart_reader) - : reader_(reader), multipart_reader_(multipart_reader) {} + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} bool operator()(MultipartContentHeader header, ContentReceiver receiver) const { - return multipart_reader_(header, receiver); + return multipart_reader_(std::move(header), std::move(receiver)); } - bool operator()(ContentReceiver receiver) const { return reader_(receiver); } + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } Reader reader_; MultipartReader multipart_reader_; @@ -341,6 +455,8 @@ struct Request { std::string remote_addr; int remote_port = -1; + std::string local_addr; + int local_port = -1; // for server std::string version; @@ -351,35 +467,35 @@ struct Request { Match matches; // for client - size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT; ResponseHandler response_handler; - ContentReceiver content_receiver; - size_t content_length = 0; - ContentProvider content_provider; + ContentReceiverWithProgress content_receiver; Progress progress; - #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - const SSL *ssl; + const SSL *ssl = nullptr; #endif - bool has_header(const char *key) const; - std::string get_header_value(const char *key, size_t id = 0) const; + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; template - T get_header_value(const char *key, size_t id = 0) const; - size_t get_header_value_count(const char *key) const; - void set_header(const char *key, const char *val); - void set_header(const char *key, const std::string &val); + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); - bool has_param(const char *key) const; - std::string get_param_value(const char *key, size_t id = 0) const; - size_t get_param_value_count(const char *key) const; + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; bool is_multipart_form_data() const; - bool has_file(const char *key) const; - MultipartFormData get_file_value(const char *key) const; + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; size_t authorization_count_ = 0; }; @@ -389,31 +505,30 @@ struct Response { std::string reason; Headers headers; std::string body; + std::string location; // Redirect location - bool has_header(const char *key) const; - std::string get_header_value(const char *key, size_t id = 0) const; + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; template - T get_header_value(const char *key, size_t id = 0) const; - size_t get_header_value_count(const char *key) const; - void set_header(const char *key, const char *val); - void set_header(const char *key, const std::string &val); + T get_header_value(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); - void set_redirect(const char *url, int status = 302); void set_redirect(const std::string &url, int status = 302); - void set_content(const char *s, size_t n, const char *content_type); - void set_content(std::string s, const char *content_type); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); void set_content_provider( - size_t length, const char *content_type, ContentProvider provider, - const std::function &resource_releaser = nullptr); + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); void set_content_provider( - const char *content_type, ContentProviderWithoutLength provider, - const std::function &resource_releaser = nullptr); + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); void set_chunked_content_provider( - const char *content_type, ContentProviderWithoutLength provider, - const std::function &resource_releaser = nullptr); + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); Response() = default; Response(const Response &) = default; @@ -422,15 +537,16 @@ struct Response { Response &operator=(Response &&) = default; ~Response() { if (content_provider_resource_releaser_) { - content_provider_resource_releaser_(); + content_provider_resource_releaser_(content_provider_success_); } } // private members... size_t content_length_ = 0; ContentProvider content_provider_; - std::function content_provider_resource_releaser_; - bool is_chunked_content_provider = false; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; }; class Stream { @@ -443,9 +559,11 @@ public: virtual ssize_t read(char *ptr, size_t size) = 0; virtual ssize_t write(const char *ptr, size_t size) = 0; virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; template - ssize_t write_format(const char *fmt, const Args &... args); + ssize_t write_format(const char *fmt, const Args &...args); ssize_t write(const char *ptr); ssize_t write(const std::string &s); }; @@ -458,7 +576,7 @@ public: virtual void enqueue(std::function fn) = 0; virtual void shutdown() = 0; - virtual void on_idle(){}; + virtual void on_idle() {} }; class ThreadPool : public TaskQueue { @@ -474,8 +592,11 @@ public: ~ThreadPool() override = default; void enqueue(std::function fn) override { - std::unique_lock lock(mutex_); - jobs_.push_back(fn); + { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + } + cond_.notify_one(); } @@ -509,7 +630,7 @@ private: if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } - fn = pool_.jobs_.front(); + fn = std::move(pool_.jobs_.front()); pool_.jobs_.pop_front(); } @@ -535,29 +656,25 @@ using Logger = std::function; using SocketOptions = std::function; -inline void default_socket_options(socket_t sock) { - int yes = 1; -#ifdef _WIN32 - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), - sizeof(yes)); - setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, - reinterpret_cast(&yes), sizeof(yes)); -#else -#ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), - sizeof(yes)); -#else - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), - sizeof(yes)); -#endif -#endif -} +void default_socket_options(socket_t sock); class Server { public: using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + using HandlerWithContentReader = std::function; + using Expect100ContinueHandler = std::function; @@ -567,46 +684,66 @@ public: virtual bool is_valid() const; - Server &Get(const char *pattern, Handler handler); - Server &Post(const char *pattern, Handler handler); - Server &Post(const char *pattern, HandlerWithContentReader handler); - Server &Put(const char *pattern, Handler handler); - Server &Put(const char *pattern, HandlerWithContentReader handler); - Server &Patch(const char *pattern, Handler handler); - Server &Patch(const char *pattern, HandlerWithContentReader handler); - Server &Delete(const char *pattern, Handler handler); - Server &Delete(const char *pattern, HandlerWithContentReader handler); - Server &Options(const char *pattern, Handler handler); + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); - bool set_base_dir(const char *dir, const char *mount_point = nullptr); - bool set_mount_point(const char *mount_point, const char *dir); - bool remove_mount_point(const char *mount_point); - void set_file_extension_and_mimetype_mapping(const char *ext, - const char *mime); - void set_file_request_handler(Handler handler); + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_file_request_handler(Handler handler); - void set_error_handler(Handler handler); - void set_expect_100_continue_handler(Expect100ContinueHandler handler); - void set_logger(Logger logger); + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); - void set_tcp_nodelay(bool on); - void set_socket_options(SocketOptions socket_options); + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); - void set_keep_alive_max_count(size_t count); - void set_keep_alive_timeout(time_t sec); - void set_read_timeout(time_t sec, time_t usec = 0); - void set_write_timeout(time_t sec, time_t usec = 0); - void set_idle_interval(time_t sec, time_t usec = 0); + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); - void set_payload_max_length(size_t length); + Server &set_default_headers(Headers headers); - bool bind_to_port(const char *host, int port, int socket_flags = 0); - int bind_to_any_port(const char *host, int socket_flags = 0); + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); bool listen_after_bind(); - bool listen(const char *host, int port, int socket_flags = 0); + bool listen(const std::string &host, int port, int socket_flags = 0); bool is_running() const; + void wait_until_ready() const; void stop(); std::function new_task_queue; @@ -616,7 +753,7 @@ protected: bool &connection_closed, const std::function &setup_request); - std::atomic svr_sock_; + std::atomic svr_sock_{INVALID_SOCKET}; size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; @@ -632,13 +769,15 @@ private: using HandlersForContentReader = std::vector>; - socket_t create_server_socket(const char *host, int port, int socket_flags, + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, SocketOptions socket_options) const; - int bind_internal(const char *host, int port, int socket_flags); + int bind_internal(const std::string &host, int port, int socket_flags); bool listen_internal(); bool routing(Request &req, Response &res, Stream &strm); - bool handle_file_request(Request &req, Response &res, bool head = false); + bool handle_file_request(const Request &req, Response &res, + bool head = false); bool dispatch_request(Request &req, Response &res, const Handlers &handlers); bool dispatch_request_for_content_reader(Request &req, Response &res, @@ -646,8 +785,15 @@ private: const HandlersForContentReader &handlers); bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); bool write_response(Stream &strm, bool close_connection, const Request &req, Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); bool write_content_with_provider(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type); @@ -659,13 +805,20 @@ private: ContentReceiver multipart_receiver); bool read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader mulitpart_header, + MultipartContentHeader multipart_header, ContentReceiver multipart_receiver); virtual bool process_and_close_socket(socket_t sock); - std::atomic is_running_; - std::vector> base_dirs_; + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + + std::atomic is_running_{false}; + std::atomic done_{false}; std::map file_extension_and_mimetype_map_; Handler file_request_handler_; Handlers get_handlers_; @@ -678,15 +831,21 @@ private: Handlers delete_handlers_; HandlersForContentReader delete_handlers_for_content_reader_; Handlers options_handlers_; - Handler error_handler_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; Logger logger_; Expect100ContinueHandler expect_100_continue_handler_; + int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; }; -enum Error { +enum class Error { Success = 0, Unknown, Connection, @@ -697,24 +856,51 @@ enum Error { Canceled, SSLConnection, SSLLoadingCerts, - SSLServerVerification + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + + // For internal use only + SSLPeerCouldBeClosed_, }; +std::string to_string(const Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + class Result { public: - Result(const std::shared_ptr &res, Error err) - : res_(res), err_(err) {} + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response operator bool() const { return res_ != nullptr; } bool operator==(std::nullptr_t) const { return res_ == nullptr; } bool operator!=(std::nullptr_t) const { return res_ != nullptr; } const Response &value() const { return *res_; } + Response &value() { return *res_; } const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error Error error() const { return err_; } + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + template + T get_request_header_value(const std::string &key, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + private: - std::shared_ptr res_; + std::unique_ptr res_; Error err_; + Headers request_headers_; }; class ClientImpl { @@ -731,112 +917,206 @@ public: virtual bool is_valid() const; - Result Get(const char *path); - Result Get(const char *path, const Headers &headers); - Result Get(const char *path, Progress progress); - Result Get(const char *path, const Headers &headers, Progress progress); - Result Get(const char *path, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, - ContentReceiver content_receiver); - Result Get(const char *path, ContentReceiver content_receiver, + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, Progress progress); - Result Get(const char *path, const Headers &headers, - ContentReceiver content_receiver, Progress progress); - Result Get(const char *path, ResponseHandler response_handler, + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver); - Result Get(const char *path, ResponseHandler response_handler, + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); - Result Get(const char *path, const Headers &headers, + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); - Result Head(const char *path); - Result Head(const char *path, const Headers &headers); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); - Result Post(const char *path); - Result Post(const char *path, const std::string &body, - const char *content_type); - Result Post(const char *path, const Headers &headers, const std::string &body, - const char *content_type); - Result Post(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Post(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Post(const char *path, const Params ¶ms); - Result Post(const char *path, const Headers &headers, const Params ¶ms); - Result Post(const char *path, const MultipartFormDataItems &items); - Result Post(const char *path, const Headers &headers, + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); - Result Put(const char *path); - Result Put(const char *path, const std::string &body, - const char *content_type); - Result Put(const char *path, const Headers &headers, const std::string &body, - const char *content_type); - Result Put(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Put(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Put(const char *path, const Params ¶ms); - Result Put(const char *path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); - Result Patch(const char *path, const std::string &body, - const char *content_type); - Result Patch(const char *path, const Headers &headers, - const std::string &body, const char *content_type); - Result Patch(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Patch(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); - Result Delete(const char *path); - Result Delete(const char *path, const std::string &body, - const char *content_type); - Result Delete(const char *path, const Headers &headers); - Result Delete(const char *path, const Headers &headers, - const std::string &body, const char *content_type); + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); - Result Options(const char *path); - Result Options(const char *path, const Headers &headers); + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); - bool send(const Request &req, Response &res); + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); size_t is_socket_open() const; + socket_t socket() const; + void stop(); + void set_hostname_addr_map(std::map addr_map); + void set_default_headers(Headers headers); + void set_address_family(int family); void set_tcp_nodelay(bool on); void set_socket_options(SocketOptions socket_options); void set_connection_timeout(time_t sec, time_t usec = 0); - void set_read_timeout(time_t sec, time_t usec = 0); - void set_write_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); - void set_basic_auth(const char *username, const char *password); - void set_bearer_token_auth(const char *token); + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_digest_auth(const char *username, const char *password); + void set_digest_auth(const std::string &username, + const std::string &password); #endif void set_keep_alive(bool on); void set_follow_location(bool on); + void set_url_encode(bool on); + void set_compress(bool on); void set_decompress(bool on); - void set_interface(const char *intf); + void set_interface(const std::string &intf); - void set_proxy(const char *host, int port); - void set_proxy_basic_auth(const char *username, const char *password); - void set_proxy_bearer_token_auth(const char *token); + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_proxy_digest_auth(const char *username, const char *password); + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -855,20 +1135,28 @@ protected: bool is_open() const { return sock != INVALID_SOCKET; } }; - virtual bool create_and_connect_socket(Socket &socket); - virtual void close_socket(Socket &socket, bool process_socket_ret); + virtual bool create_and_connect_socket(Socket &socket, Error &error); - bool process_request(Stream &strm, const Request &req, Response &res, - bool close_connection); + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); - Error get_last_error() const; + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); void copy_settings(const ClientImpl &rhs); - // Error state - mutable Error error_ = Error::Success; - - // Socket endoint information + // Socket endpoint information const std::string host_; const int port_; const std::string host_and_port_; @@ -878,6 +1166,14 @@ protected: mutable std::mutex socket_mutex_; std::recursive_mutex request_mutex_; + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + // Default headers Headers default_headers_; @@ -903,6 +1199,9 @@ protected: bool keep_alive_ = false; bool follow_location_ = false; + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; SocketOptions socket_options_ = nullptr; @@ -922,6 +1221,13 @@ protected: std::string proxy_digest_auth_password_; #endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT bool server_certificate_verification_ = true; #endif @@ -929,19 +1235,34 @@ protected: Logger logger_; private: - socket_t create_client_socket() const; - bool read_response_line(Stream &strm, Response &res); - bool write_request(Stream &strm, const Request &req, bool close_connection); - bool redirect(const Request &req, Response &res); - bool handle_request(Stream &strm, const Request &req, Response &res, - bool close_connection); - void stop_core(); - std::shared_ptr send_with_content_provider( - const char *method, const char *path, const Headers &headers, - const std::string &body, size_t content_length, - ContentProvider content_provider, const char *content_type); + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); - virtual bool process_socket(Socket &socket, + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, std::function callback); virtual bool is_ssl() const; }; @@ -949,9 +1270,9 @@ private: class Client { public: // Universal interface - explicit Client(const char *scheme_host_port); + explicit Client(const std::string &scheme_host_port); - explicit Client(const char *scheme_host_port, + explicit Client(const std::string &scheme_host_port, const std::string &client_cert_path, const std::string &client_key_path); @@ -962,114 +1283,206 @@ public: const std::string &client_cert_path, const std::string &client_key_path); + Client(Client &&) = default; + ~Client(); bool is_valid() const; - Result Get(const char *path); - Result Get(const char *path, const Headers &headers); - Result Get(const char *path, Progress progress); - Result Get(const char *path, const Headers &headers, Progress progress); - Result Get(const char *path, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, - ContentReceiver content_receiver); - Result Get(const char *path, ContentReceiver content_receiver, + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, Progress progress); - Result Get(const char *path, const Headers &headers, - ContentReceiver content_receiver, Progress progress); - Result Get(const char *path, ResponseHandler response_handler, + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver); - Result Get(const char *path, const Headers &headers, + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); - Result Get(const char *path, ResponseHandler response_handler, + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); - Result Head(const char *path); - Result Head(const char *path, const Headers &headers); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); - Result Post(const char *path); - Result Post(const char *path, const std::string &body, - const char *content_type); - Result Post(const char *path, const Headers &headers, const std::string &body, - const char *content_type); - Result Post(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Post(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Post(const char *path, const Params ¶ms); - Result Post(const char *path, const Headers &headers, const Params ¶ms); - Result Post(const char *path, const MultipartFormDataItems &items); - Result Post(const char *path, const Headers &headers, + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); - Result Put(const char *path); - Result Put(const char *path, const std::string &body, - const char *content_type); - Result Put(const char *path, const Headers &headers, const std::string &body, - const char *content_type); - Result Put(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Put(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Put(const char *path, const Params ¶ms); - Result Put(const char *path, const Headers &headers, const Params ¶ms); - Result Patch(const char *path, const std::string &body, - const char *content_type); - Result Patch(const char *path, const Headers &headers, - const std::string &body, const char *content_type); - Result Patch(const char *path, size_t content_length, - ContentProvider content_provider, const char *content_type); - Result Patch(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); - Result Delete(const char *path); - Result Delete(const char *path, const std::string &body, - const char *content_type); - Result Delete(const char *path, const Headers &headers); - Result Delete(const char *path, const Headers &headers, - const std::string &body, const char *content_type); + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); - Result Options(const char *path); - Result Options(const char *path, const Headers &headers); + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); - bool send(const Request &req, Response &res); + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); size_t is_socket_open() const; + socket_t socket() const; + void stop(); + void set_hostname_addr_map(std::map addr_map); + void set_default_headers(Headers headers); + void set_address_family(int family); void set_tcp_nodelay(bool on); void set_socket_options(SocketOptions socket_options); void set_connection_timeout(time_t sec, time_t usec = 0); - void set_read_timeout(time_t sec, time_t usec = 0); - void set_write_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); - void set_basic_auth(const char *username, const char *password); - void set_bearer_token_auth(const char *token); + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_digest_auth(const char *username, const char *password); + void set_digest_auth(const std::string &username, + const std::string &password); #endif void set_keep_alive(bool on); void set_follow_location(bool on); + void set_url_encode(bool on); + void set_compress(bool on); void set_decompress(bool on); - void set_interface(const char *intf); + void set_interface(const std::string &intf); - void set_proxy(const char *host, int port); - void set_proxy_basic_auth(const char *username, const char *password); - void set_proxy_bearer_token_auth(const char *token); + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_proxy_digest_auth(const char *username, const char *password); + void set_proxy_digest_auth(const std::string &username, + const std::string &password); #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1080,8 +1493,8 @@ public: // SSL #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_ca_cert_path(const char *ca_cert_file_path, - const char *ca_cert_dir_path = nullptr); + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); void set_ca_cert_store(X509_STORE *ca_cert_store); @@ -1091,27 +1504,33 @@ public: #endif private: - std::shared_ptr cli_; + std::unique_ptr cli_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT bool is_ssl_ = false; #endif -}; // namespace httplib +}; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLServer : public Server { public: SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path = nullptr, - const char *client_ca_cert_dir_path = nullptr); + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr); + SSLServer( + const std::function &setup_ssl_ctx_callback); + ~SSLServer() override; bool is_valid() const override; + SSL_CTX *ssl_context() const; + private: bool process_and_close_socket(socket_t sock) override; @@ -1136,9 +1555,6 @@ public: bool is_valid() const override; - void set_ca_cert_path(const char *ca_cert_file_path, - const char *ca_cert_dir_path = nullptr); - void set_ca_cert_store(X509_STORE *ca_cert_store); long get_openssl_verify_result() const; @@ -1146,15 +1562,17 @@ public: SSL_CTX *ssl_context() const; private: - bool create_and_connect_socket(Socket &socket) override; - void close_socket(Socket &socket, bool process_socket_ret) override; + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); - bool process_socket(Socket &socket, + bool process_socket(const Socket &socket, std::function callback) override; bool is_ssl() const override; - bool connect_with_proxy(Socket &sock, Response &res, bool &success); - bool initialize_ssl(Socket &socket); + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); bool load_certs(); @@ -1169,19 +1587,397 @@ private: std::vector host_components_; - std::string ca_cert_file_path_; - std::string ca_cert_dir_path_; - X509_STORE *ca_cert_store_ = nullptr; long verify_result_ = 0; friend class ClientImpl; }; #endif +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +template +inline T get_header_value(const Headers & /*headers*/, + const std::string & /*key*/, size_t /*id*/ = 0, + uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +template +inline T Request::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline T Response::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +template +inline T Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(Ranges ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + virtual ~nocompressor() = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor(); + ~gzip_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +} // namespace detail + // ---------------------------------------------------------------------------- /* - * Implementation + * Implementation that will be part of the .cc file if split into .h + .cc. */ namespace detail { @@ -1227,14 +2023,6 @@ inline std::string from_i_to_hex(size_t n) { return ret; } -inline bool start_with(const std::string &a, const std::string &b) { - if (a.size() < b.size()) { return false; } - for (size_t i = 0; i < b.size(); i++) { - if (std::tolower(a[i]) != std::tolower(b[i])) { return false; } - } - return true; -} - inline size_t to_utf8(int code, char *buff) { if (code < 0x0080) { buff[0] = (code & 0x7F); @@ -1298,8 +2086,12 @@ inline std::string base64_encode(const std::string &in) { } inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else struct stat st; return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif } inline bool is_dir(const std::string &path) { @@ -1344,8 +2136,30 @@ inline bool is_valid_path(const std::string &path) { return true; } +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + inline std::string encode_url(const std::string &s) { std::string result; + result.reserve(s.size()); for (size_t i = 0; s[i]; i++) { switch (s[i]) { @@ -1446,7 +2260,8 @@ inline std::string trim_copy(const std::string &s) { return s.substr(r.first, r.second - r.first); } -template void split(const char *b, const char *e, char d, Fn fn) { +inline void split(const char *b, const char *e, char d, + std::function fn) { size_t i = 0; size_t beg = 0; @@ -1465,81 +2280,70 @@ template void split(const char *b, const char *e, char d, Fn fn) { } } -// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` -// to store data. The call can set memory on stack for performance. -class stream_line_reader { -public: - stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size) - : strm_(strm), fixed_buffer_(fixed_buffer), - fixed_buffer_size_(fixed_buffer_size) {} +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} - const char *ptr() const { - if (glowable_buffer_.empty()) { - return fixed_buffer_; - } else { - return glowable_buffer_.data(); - } +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); } +} - size_t size() const { - if (glowable_buffer_.empty()) { - return fixed_buffer_used_size_; - } else { - return glowable_buffer_.size(); - } +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); } +} - bool end_with_crlf() const { - auto end = ptr() + size(); - return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; - } +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} - bool getline() { - fixed_buffer_used_size_ = 0; - glowable_buffer_.clear(); +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); - for (size_t i = 0;; i++) { - char byte; - auto n = strm_.read(&byte, 1); + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); - if (n < 0) { + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { return false; - } else if (n == 0) { - if (i == 0) { - return false; - } else { - break; - } + } else { + break; } - - append(byte); - - if (byte == '\n') { break; } } - return true; + append(byte); + + if (byte == '\n') { break; } } -private: - void append(char c) { - if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { - fixed_buffer_[fixed_buffer_used_size_++] = c; - fixed_buffer_[fixed_buffer_used_size_] = '\0'; - } else { - if (glowable_buffer_.empty()) { - assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); - glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); - } - glowable_buffer_ += c; + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); } + glowable_buffer_ += c; } - - Stream &strm_; - char *fixed_buffer_; - const size_t fixed_buffer_size_; - size_t fixed_buffer_used_size_ = 0; - std::string glowable_buffer_; -}; +} inline int close_socket(socket_t sock) { #ifdef _WIN32 @@ -1559,6 +2363,31 @@ template inline ssize_t handle_EINTR(T fn) { return res; } +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; @@ -1569,6 +2398,10 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); #else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + fd_set fds; FD_ZERO(&fds); FD_SET(sock, &fds); @@ -1593,6 +2426,10 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); #else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + fd_set fds; FD_ZERO(&fds); FD_SET(sock, &fds); @@ -1607,7 +2444,8 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { #endif } -inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; @@ -1617,15 +2455,23 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + if (poll_res == 0) { return Error::ConnectionTimeout; } + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { int error = 0; socklen_t len = sizeof(error); auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); - return res >= 0 && !error; + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; } - return false; + + return Error::Connection; #else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + fd_set fdsr; FD_ZERO(&fdsr); FD_SET(sock, &fdsr); @@ -1641,17 +2487,31 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); }); + if (ret == 0) { return Error::ConnectionTimeout; } + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { int error = 0; socklen_t len = sizeof(error); - return getsockopt(sock, SOL_SOCKET, SO_ERROR, - reinterpret_cast(&error), &len) >= 0 && - !error; + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; } - return false; + return Error::Connection; #endif } +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + class SocketStream : public Stream { public: SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, @@ -1663,6 +2523,8 @@ public: ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; private: socket_t sock_; @@ -1670,6 +2532,12 @@ private: time_t read_timeout_usec_; time_t write_timeout_sec_; time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024 * 4; }; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1685,6 +2553,8 @@ public: ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; private: socket_t sock_; @@ -1696,24 +2566,6 @@ private: }; #endif -class BufferStream : public Stream { -public: - BufferStream() = default; - ~BufferStream() override = default; - - bool is_readable() const override; - bool is_writable() const override; - ssize_t read(char *ptr, size_t size) override; - ssize_t write(const char *ptr, size_t size) override; - void get_remote_ip_and_port(std::string &ip, int &port) const override; - - const std::string &get_buffer() const; - -private: - std::string buffer; - size_t position = 0; -}; - inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { using namespace std::chrono; auto start = steady_clock::now(); @@ -1735,12 +2587,14 @@ inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { template inline bool -process_server_socket_core(socket_t sock, size_t keep_alive_max_count, +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, T callback) { assert(keep_alive_max_count > 0); auto ret = false; auto count = keep_alive_max_count; - while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) { + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { auto close_connection = count == 1; auto connection_closed = false; ret = callback(close_connection, connection_closed); @@ -1752,12 +2606,13 @@ process_server_socket_core(socket_t sock, size_t keep_alive_max_count, template inline bool -process_server_socket(socket_t sock, size_t keep_alive_max_count, +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, T callback) { return process_server_socket_core( - sock, keep_alive_max_count, keep_alive_timeout_sec, + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, [&](bool close_connection, bool &connection_closed) { SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); @@ -1765,11 +2620,11 @@ process_server_socket(socket_t sock, size_t keep_alive_max_count, }); } -template inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec, T callback) { + time_t write_timeout_usec, + std::function callback) { SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); return callback(strm); @@ -1784,23 +2639,61 @@ inline int shutdown_socket(socket_t sock) { } template -socket_t create_socket(const char *host, int port, int socket_flags, - bool tcp_nodelay, SocketOptions socket_options, +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, BindOrConnect bind_or_connect) { // Get address info + const char *node = nullptr; struct addrinfo hints; struct addrinfo *result; memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = socket_flags; hints.ai_protocol = 0; + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET; + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + auto service = std::to_string(port); - if (getaddrinfo(host, service.c_str(), &hints, &result)) { -#ifdef __linux__ + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ res_init(); #endif return INVALID_SOCKET; @@ -1809,8 +2702,9 @@ socket_t create_socket(const char *host, int port, int socket_flags, for (auto rp = result; rp; rp = rp->ai_next) { // Create a socket #ifdef _WIN32 - auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, - nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT); + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); /** * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 * and above the socket creation fails on older Windows Systems. @@ -1834,7 +2728,10 @@ socket_t create_socket(const char *host, int port, int socket_flags, if (sock == INVALID_SOCKET) { continue; } #ifndef _WIN32 - if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } #endif if (tcp_nodelay) { @@ -1883,7 +2780,7 @@ inline bool is_connection_error() { #endif } -inline bool bind_ip_address(socket_t sock, const char *host) { +inline bool bind_ip_address(socket_t sock, const std::string &host) { struct addrinfo hints; struct addrinfo *result; @@ -1892,7 +2789,7 @@ inline bool bind_ip_address(socket_t sock, const char *host) { hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; - if (getaddrinfo(host, "0", &hints, &result)) { return false; } + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } auto ret = false; for (auto rp = result; rp; rp = rp->ai_next) { @@ -1907,16 +2804,19 @@ inline bool bind_ip_address(socket_t sock, const char *host) { return ret; } -#if !defined _WIN32 && !defined ANDROID +#if !defined _WIN32 && !defined ANDROID && !defined _AIX #define USE_IF2IP #endif #ifdef USE_IF2IP -inline std::string if2ip(const std::string &ifn) { +inline std::string if2ip(int address_family, const std::string &ifn) { struct ifaddrs *ifap; getifaddrs(&ifap); + std::string addr_candidate; for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_addr && ifn == ifa->ifa_name) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { if (ifa->ifa_addr->sa_family == AF_INET) { auto sa = reinterpret_cast(ifa->ifa_addr); char buf[INET_ADDRSTRLEN]; @@ -1924,48 +2824,94 @@ inline std::string if2ip(const std::string &ifn) { freeifaddrs(ifap); return std::string(buf, INET_ADDRSTRLEN); } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } } } } freeifaddrs(ifap); - return std::string(); + return addr_candidate; } #endif -inline socket_t create_client_socket(const char *host, int port, - bool tcp_nodelay, - SocketOptions socket_options, - time_t timeout_sec, time_t timeout_usec, - const std::string &intf, Error &error) { +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { auto sock = create_socket( - host, port, 0, tcp_nodelay, socket_options, - [&](socket_t sock, struct addrinfo &ai) -> bool { + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { if (!intf.empty()) { #ifdef USE_IF2IP - auto ip = if2ip(intf); - if (ip.empty()) { ip = intf; } - if (!bind_ip_address(sock, ip.c_str())) { + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { error = Error::BindIPAddress; return false; } #endif } - set_nonblocking(sock, true); + set_nonblocking(sock2, true); auto ret = - ::connect(sock, ai.ai_addr, static_cast(ai.ai_addrlen)); + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); if (ret < 0) { - if (is_connection_error() || - !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) { - close_socket(sock); + if (is_connection_error()) { error = Error::Connection; return false; } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif } - set_nonblocking(sock, false); error = Error::Success; return true; }); @@ -1979,21 +2925,34 @@ inline socket_t create_client_socket(const char *host, int port, return sock; } -inline void get_remote_ip_and_port(const struct sockaddr_storage &addr, - socklen_t addr_len, std::string &ip, - int &port) { +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { if (addr.ss_family == AF_INET) { port = ntohs(reinterpret_cast(&addr)->sin_port); } else if (addr.ss_family == AF_INET6) { port = ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; } std::array ipstr{}; - if (!getnameinfo(reinterpret_cast(&addr), addr_len, - ipstr.data(), static_cast(ipstr.size()), nullptr, - 0, NI_NUMERICHOST)) { - ip = ipstr.data(); + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); } } @@ -2003,10 +2962,52 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { if (!getpeername(sock, reinterpret_cast(&addr), &addr_len)) { - get_remote_ip_and_port(addr, addr_len, ip, port); +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); } } +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + inline const char * find_content_type(const std::string &path, const std::map &user_data) { @@ -2015,36 +3016,60 @@ find_content_type(const std::string &path, auto it = user_data.find(ext); if (it != user_data.end()) { return it->second.c_str(); } - if (ext == "txt") { - return "text/plain"; - } else if (ext == "html" || ext == "htm") { - return "text/html"; - } else if (ext == "css") { - return "text/css"; - } else if (ext == "jpeg" || ext == "jpg") { - return "image/jpg"; - } else if (ext == "png") { - return "image/png"; - } else if (ext == "gif") { - return "image/gif"; - } else if (ext == "svg") { - return "image/svg+xml"; - } else if (ext == "ico") { - return "image/x-icon"; - } else if (ext == "json") { - return "application/json"; - } else if (ext == "pdf") { - return "application/pdf"; - } else if (ext == "js") { - return "application/javascript"; - } else if (ext == "wasm") { - return "application/wasm"; - } else if (ext == "xml") { - return "application/xml"; - } else if (ext == "xhtml") { - return "application/xhtml+xml"; + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; } - return nullptr; } inline const char *status_message(int status) { @@ -2118,15 +3143,22 @@ inline const char *status_message(int status) { } inline bool can_compress_content_type(const std::string &content_type) { - return (!content_type.find("text/") && content_type != "text/event-stream") || - content_type == "image/svg+xml" || - content_type == "application/javascript" || - content_type == "application/json" || - content_type == "application/xml" || - content_type == "application/xhtml+xml"; -} + using udl::operator""_t; -enum class EncodingType { None = 0, Gzip, Brotli }; + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} inline EncodingType encoding_type(const Request &req, const Response &res) { auto ret = @@ -2151,120 +3183,109 @@ inline EncodingType encoding_type(const Request &req, const Response &res) { return EncodingType::None; } -class compressor { -public: - virtual ~compressor(){}; - - typedef std::function Callback; - virtual bool compress(const char *data, size_t data_length, bool last, - Callback callback) = 0; -}; - -class decompressor { -public: - virtual ~decompressor() {} - - virtual bool is_valid() const = 0; - - typedef std::function Callback; - virtual bool decompress(const char *data, size_t data_length, - Callback callback) = 0; -}; - -class nocompressor : public compressor { -public: - ~nocompressor(){}; - - bool compress(const char *data, size_t data_length, bool /*last*/, - Callback callback) override { - if (!data_length) { return true; } - return callback(data, data_length); - } -}; +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} #ifdef CPPHTTPLIB_ZLIB_SUPPORT -class gzip_compressor : public compressor { -public: - gzip_compressor() { - std::memset(&strm_, 0, sizeof(strm_)); - strm_.zalloc = Z_NULL; - strm_.zfree = Z_NULL; - strm_.opaque = Z_NULL; +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; - is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, - Z_DEFAULT_STRATEGY) == Z_OK; - } + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} - ~gzip_compressor() { deflateEnd(&strm_); } +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } - bool compress(const char *data, size_t data_length, bool last, - Callback callback) override { - assert(is_valid_); +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); - auto flush = last ? Z_FINISH : Z_NO_FLUSH; + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); - strm_.avail_in = static_cast(data_length); + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); strm_.next_in = const_cast(reinterpret_cast(data)); + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; int ret = Z_OK; std::array buff{}; do { - strm_.avail_out = buff.size(); + strm_.avail_out = static_cast(buff.size()); strm_.next_out = reinterpret_cast(buff.data()); ret = deflate(&strm_, flush); - assert(ret != Z_STREAM_ERROR); + if (ret == Z_STREAM_ERROR) { return false; } if (!callback(buff.data(), buff.size() - strm_.avail_out)) { return false; } } while (strm_.avail_out == 0); - assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK)); + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); assert(strm_.avail_in == 0); - return true; - } + } while (data_length > 0); -private: - bool is_valid_ = false; - z_stream strm_; -}; + return true; +} -class gzip_decompressor : public decompressor { -public: - gzip_decompressor() { - std::memset(&strm_, 0, sizeof(strm_)); - strm_.zalloc = Z_NULL; - strm_.zfree = Z_NULL; - strm_.opaque = Z_NULL; +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; - // 15 is the value of wbits, which should be at the maximum possible value - // to ensure that any gzip stream can be decoded. The offset of 32 specifies - // that the stream type should be automatically detected either gzip or - // deflate. - is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; - } + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} - ~gzip_decompressor() { inflateEnd(&strm_); } +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } - bool is_valid() const override { return is_valid_; } +inline bool gzip_decompressor::is_valid() const { return is_valid_; } - bool decompress(const char *data, size_t data_length, - Callback callback) override { - assert(is_valid_); +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); - int ret = Z_OK; + int ret = Z_OK; - strm_.avail_in = static_cast(data_length); + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); strm_.next_in = const_cast(reinterpret_cast(data)); + data_length -= strm_.avail_in; + data += strm_.avail_in; + std::array buff{}; while (strm_.avail_in > 0) { - strm_.avail_out = buff.size(); + strm_.avail_out = static_cast(buff.size()); strm_.next_out = reinterpret_cast(buff.data()); + auto prev_avail_in = strm_.avail_in; + ret = inflate(&strm_, Z_NO_FLUSH); + + if (prev_avail_in - strm_.avail_in == 0) { return false; } + assert(ret != Z_STREAM_ERROR); switch (ret) { case Z_NEED_DICT: @@ -2277,118 +3298,107 @@ public: } } - return ret == Z_OK || ret == Z_STREAM_END; - } + if (ret != Z_OK && ret != Z_STREAM_END) return false; -private: - bool is_valid_ = false; - z_stream strm_; -}; + } while (data_length > 0); + + return true; +} #endif #ifdef CPPHTTPLIB_BROTLI_SUPPORT -class brotli_compressor : public compressor { -public: - brotli_compressor() { - state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); - } +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} - ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); } +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} - bool compress(const char *data, size_t data_length, bool last, - Callback callback) override { - std::array buff{}; +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; - auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; - auto available_in = data_length; - auto next_in = reinterpret_cast(data); + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); - for (;;) { - if (last) { - if (BrotliEncoderIsFinished(state_)) { break; } - } else { - if (!available_in) { break; } - } - - auto available_out = buff.size(); - auto next_out = buff.data(); - - if (!BrotliEncoderCompressStream(state_, operation, &available_in, - &next_in, &available_out, &next_out, - nullptr)) { - return false; - } - - auto output_bytes = buff.size() - available_out; - if (output_bytes) { - callback(reinterpret_cast(buff.data()), output_bytes); - } + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } } - return true; - } + auto available_out = buff.size(); + auto next_out = buff.data(); -private: - BrotliEncoderState *state_ = nullptr; -}; - -class brotli_decompressor : public decompressor { -public: - brotli_decompressor() { - decoder_s = BrotliDecoderCreateInstance(0, 0, 0); - decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT - : BROTLI_DECODER_RESULT_ERROR; - } - - ~brotli_decompressor() { - if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } - } - - bool is_valid() const override { return decoder_s; } - - bool decompress(const char *data, size_t data_length, - Callback callback) override { - if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || - decoder_r == BROTLI_DECODER_RESULT_ERROR) { - return 0; + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; } - const uint8_t *next_in = (const uint8_t *)data; - size_t avail_in = data_length; - size_t total_out; - - decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; - - std::array buff{}; - while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { - char *next_out = buff.data(); - size_t avail_out = buff.size(); - - decoder_r = BrotliDecoderDecompressStream( - decoder_s, &avail_in, &next_in, &avail_out, - reinterpret_cast(&next_out), &total_out); - - if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } - - if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); } - - return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || - decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; } -private: - BrotliDecoderResult decoder_r; - BrotliDecoderState *decoder_s = nullptr; -}; + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} #endif -inline bool has_header(const Headers &headers, const char *key) { +inline bool has_header(const Headers &headers, const std::string &key) { return headers.find(key) != headers.end(); } -inline const char *get_header_value(const Headers &headers, const char *key, - size_t id = 0, const char *def = nullptr) { +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); @@ -2396,21 +3406,12 @@ inline const char *get_header_value(const Headers &headers, const char *key, return def; } -template -inline T get_header_value(const Headers & /*headers*/, const char * /*key*/, - size_t /*id*/ = 0, uint64_t /*def*/ = 0) {} - -template <> -inline uint64_t get_header_value(const Headers &headers, - const char *key, size_t id, - uint64_t def) { - auto rng = headers.equal_range(key); - auto it = rng.first; - std::advance(it, static_cast(id)); - if (it != rng.second) { - return std::strtoull(it->second.data(), nullptr, 10); +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } } - return def; + return true; } template @@ -2436,7 +3437,11 @@ inline bool parse_header(const char *beg, const char *end, T fn) { } if (p < end) { - fn(std::string(beg, key_end), decode_url(std::string(p, end), false)); + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); return true; } @@ -2452,15 +3457,26 @@ inline bool read_headers(Stream &strm, Headers &headers) { if (!line_reader.getline()) { return false; } // Check if the line ends with CRLF. + auto line_terminator_len = 2; if (line_reader.end_with_crlf()) { // Blank line indicates end of headers. if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else } else { continue; // Skip invalid line. } +#endif - // Exclude CRLF - auto end = line_reader.ptr() + line_reader.size() - 2; + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; parse_header(line_reader.ptr(), end, [&](std::string &&key, std::string &&val) { @@ -2472,7 +3488,8 @@ inline bool read_headers(Stream &strm, Headers &headers) { } inline bool read_content_with_length(Stream &strm, uint64_t len, - Progress progress, ContentReceiver out) { + Progress progress, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; uint64_t r = 0; @@ -2481,8 +3498,7 @@ inline bool read_content_with_length(Stream &strm, uint64_t len, auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return false; } - if (!out(buf, static_cast(n))) { return false; } - + if (!out(buf, static_cast(n), r, len)) { return false; } r += static_cast(n); if (progress) { @@ -2504,8 +3520,10 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) { } } -inline bool read_content_without_length(Stream &strm, ContentReceiver out) { +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; for (;;) { auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); if (n < 0) { @@ -2513,13 +3531,17 @@ inline bool read_content_without_length(Stream &strm, ContentReceiver out) { } else if (n == 0) { return true; } - if (!out(buf, static_cast(n))) { return false; } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); } return true; } -inline bool read_content_chunked(Stream &strm, ContentReceiver out) { +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { const auto bufsiz = 16; char buf[bufsiz]; @@ -2544,15 +3566,29 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) { if (!line_reader.getline()) { return false; } - if (strcmp(line_reader.ptr(), "\r\n")) { break; } + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } if (!line_reader.getline()) { return false; } } - if (chunk_len == 0) { - // Reader terminator after chunks - if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n")) - return false; + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } } return true; @@ -2564,23 +3600,23 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) { } template -bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver, +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, bool decompress, U callback) { if (decompress) { std::string encoding = x.get_header_value("Content-Encoding"); - std::shared_ptr decompressor; + std::unique_ptr decompressor; - if (encoding.find("gzip") != std::string::npos || - encoding.find("deflate") != std::string::npos) { + if (encoding == "gzip" || encoding == "deflate") { #ifdef CPPHTTPLIB_ZLIB_SUPPORT - decompressor = std::make_shared(); + decompressor = detail::make_unique(); #else status = 415; return false; #endif } else if (encoding.find("br") != std::string::npos) { #ifdef CPPHTTPLIB_BROTLI_SUPPORT - decompressor = std::make_shared(); + decompressor = detail::make_unique(); #else status = 415; return false; @@ -2589,12 +3625,14 @@ bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver, if (decompressor) { if (decompressor->is_valid()) { - ContentReceiver out = [&](const char *buf, size_t n) { - return decompressor->decompress( - buf, n, - [&](const char *buf, size_t n) { return receiver(buf, n); }); + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); }; - return callback(out); + return callback(std::move(out)); } else { status = 500; return false; @@ -2602,23 +3640,25 @@ bool prepare_content_receiver(T &x, int &status, ContentReceiver receiver, } } - ContentReceiver out = [&](const char *buf, size_t n) { - return receiver(buf, n); + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); }; - return callback(out); + return callback(std::move(out)); } template bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, - Progress progress, ContentReceiver receiver, + Progress progress, ContentReceiverWithProgress receiver, bool decompress) { return prepare_content_receiver( - x, status, receiver, decompress, [&](const ContentReceiver &out) { + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { auto ret = true; auto exceed_payload_max_length = false; if (is_chunked_transfer_encoding(x.headers)) { - ret = read_content_chunked(strm, out); + ret = read_content_chunked(strm, x, out); } else if (!has_header(x.headers, "Content-Length")) { ret = read_content_without_length(strm, out); } else { @@ -2628,26 +3668,17 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, skip_content_with_length(strm, len); ret = false; } else if (len > 0) { - ret = read_content_with_length(strm, len, progress, out); + ret = read_content_with_length(strm, len, std::move(progress), out); } } if (!ret) { status = exceed_payload_max_length ? 413 : 400; } return ret; }); -} +} // namespace detail -template -inline ssize_t write_headers(Stream &strm, const T &info, - const Headers &headers) { +inline ssize_t write_headers(Stream &strm, const Headers &headers) { ssize_t write_len = 0; - for (const auto &x : info.headers) { - if (x.first == "EXCEPTION_WHAT") { continue; } - auto len = - strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); - if (len < 0) { return len; } - write_len += len; - } for (const auto &x : headers) { auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); @@ -2671,99 +3702,119 @@ inline bool write_data(Stream &strm, const char *d, size_t l) { } template -inline ssize_t write_content(Stream &strm, ContentProvider content_provider, - size_t offset, size_t length, T is_shutting_down) { - size_t begin_offset = offset; +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { size_t end_offset = offset + length; auto ok = true; DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { + data_sink.write = [&](const char *d, size_t l) -> bool { if (ok) { - offset += l; - if (!write_data(strm, d, l)) { ok = false; } + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } } + return ok; }; - data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - while (offset < end_offset && !is_shutting_down()) { - if (!content_provider(offset, end_offset - offset, data_sink)) { - return -1; + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; } - if (!ok) { return -1; } } - return static_cast(offset - begin_offset); + error = Error::Success; + return true; } template -inline ssize_t write_content_without_length(Stream &strm, - ContentProvider content_provider, - T is_shutting_down) { +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { size_t offset = 0; auto data_available = true; auto ok = true; DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { + data_sink.write = [&](const char *d, size_t l) -> bool { if (ok) { offset += l; - if (!write_data(strm, d, l)) { ok = false; } + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } } + return ok; }; data_sink.done = [&](void) { data_available = false; }; - data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - while (data_available && !is_shutting_down()) { - if (!content_provider(offset, 0, data_sink)) { return -1; } - if (!ok) { return -1; } + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } } - - return static_cast(offset); + return true; } template -inline ssize_t write_content_chunked(Stream &strm, - ContentProvider content_provider, - T is_shutting_down, U &compressor) { +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { size_t offset = 0; auto data_available = true; - ssize_t total_written_length = 0; auto ok = true; DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { - if (!ok) { return; } + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; - data_available = l > 0; - offset += l; - - std::string payload; - if (!compressor.compress(d, l, false, - [&](const char *data, size_t data_len) { - payload.append(data, data_len); - return true; - })) { - ok = false; - return; - } - - if (!payload.empty()) { - // Emit chunked response header and footer for each chunk - auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (write_data(strm, chunk.data(), chunk.size())) { - total_written_length += chunk.size(); + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } } else { ok = false; - return; } } + return ok; }; - data_sink.done = [&](void) { + auto done_with_trailer = [&](const Headers *trailer) { if (!ok) { return; } data_available = false; @@ -2781,38 +3832,71 @@ inline ssize_t write_content_chunked(Stream &strm, if (!payload.empty()) { // Emit chunked response header and footer for each chunk auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (write_data(strm, chunk.data(), chunk.size())) { - total_written_length += chunk.size(); - } else { + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { ok = false; return; } } - static const std::string done_marker("0\r\n\r\n"); - if (write_data(strm, done_marker.data(), done_marker.size())) { - total_written_length += done_marker.size(); - } else { + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { ok = false; } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } }; - data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; while (data_available && !is_shutting_down()) { - if (!content_provider(offset, 0, data_sink)) { return -1; } - if (!ok) { return -1; } + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } } - return total_written_length; + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); } template -inline bool redirect(T &cli, const Request &req, Response &res, - const std::string &path) { +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { Request new_req = req; new_req.path = path; - new_req.redirect_count -= 1; + new_req.redirect_count_ -= 1; if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { new_req.method = "GET"; @@ -2822,8 +3906,12 @@ inline bool redirect(T &cli, const Request &req, Response &res, Response new_res; - auto ret = cli.send(new_req, new_res); - if (ret) { res = new_res; } + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } return ret; } @@ -2834,13 +3922,18 @@ inline std::string params_to_query_str(const Params ¶ms) { if (it != params.begin()) { query += "&"; } query += it->first; query += "="; - query += encode_url(it->second); + query += encode_query_param(it->second); } return query; } inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + std::string key; std::string val; split(b, e, '=', [&](const char *b2, const char *e2) { @@ -2859,9 +3952,12 @@ inline void parse_query_text(const std::string &s, Params ¶ms) { inline bool parse_multipart_boundary(const std::string &content_type, std::string &boundary) { - auto pos = content_type.find("boundary="); + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); if (pos == std::string::npos) { return false; } - boundary = content_type.substr(pos + 9); + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = content_type.substr(beg, end - beg); if (boundary.length() >= 2 && boundary.front() == '"' && boundary.back() == '"') { boundary = boundary.substr(1, boundary.size() - 2); @@ -2869,7 +3965,11 @@ inline bool parse_multipart_boundary(const std::string &content_type, return !boundary.empty(); } +#ifdef CPPHTTPLIB_NO_EXCEPTIONS inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); std::smatch m; if (std::regex_match(s, m, re_first_range)) { @@ -2901,37 +4001,41 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) { return all_valid_ranges; } return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS } +#else +} catch (...) { return false; } +#endif class MultipartFormDataParser { public: MultipartFormDataParser() = default; - void set_boundary(std::string &&boundary) { boundary_ = boundary; } + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } bool is_valid() const { return is_valid_; } - template - bool parse(const char *buf, size_t n, T content_callback, U header_callback) { + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + // TODO: support 'filename*' static const std::regex re_content_disposition( - "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename=" - "\"(.*?)\")?\\s*$", + R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~", std::regex_constants::icase); - static const std::string dash_ = "--"; - static const std::string crlf_ = "\r\n"; - buf_.append(buf, n); // TODO: performance improvement + buf_append(buf, n); - while (!buf_.empty()) { + while (buf_size() > 0) { switch (state_) { case 0: { // Initial boundary - auto pattern = dash_ + boundary_ + crlf_; - if (pattern.size() > buf_.size()) { return true; } - auto pos = buf_.find(pattern); - if (pos != 0) { return false; } - buf_.erase(0, pattern.size()); - off_ += pattern.size(); + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); state_ = 1; break; } @@ -2941,113 +4045,80 @@ public: break; } case 2: { // Headers - auto pos = buf_.find(crlf_); - while (pos != std::string::npos) { + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { // Empty line if (pos == 0) { if (!header_callback(file_)) { is_valid_ = false; return false; } - buf_.erase(0, crlf_.size()); - off_ += crlf_.size(); + buf_erase(crlf_.size()); state_ = 3; break; } static const std::string header_name = "content-type:"; - const auto header = buf_.substr(0, pos); - if (start_with(header, header_name)) { + const auto header = buf_head(pos); + if (start_with_case_ignore(header, header_name)) { file_.content_type = trim_copy(header.substr(header_name.size())); } else { std::smatch m; if (std::regex_match(header, m, re_content_disposition)) { file_.name = m[1]; file_.filename = m[2]; + } else { + is_valid_ = false; + return false; } } - - buf_.erase(0, pos + crlf_.size()); - off_ += pos + crlf_.size(); - pos = buf_.find(crlf_); + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); } if (state_ != 3) { return true; } break; } case 3: { // Body - { - auto pattern = crlf_ + dash_; - if (pattern.size() > buf_.size()) { return true; } - - auto pos = buf_.find(pattern); - if (pos == std::string::npos) { - pos = buf_.size(); - while (pos > 0) { - auto c = buf_[pos - 1]; - if (c != '\r' && c != '\n' && c != '-') { break; } - pos--; - } - } - - if (!content_callback(buf_.data(), pos)) { + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { is_valid_ = false; return false; } - - off_ += pos; - buf_.erase(0, pos); - } - - { - auto pattern = crlf_ + dash_ + boundary_; - if (pattern.size() > buf_.size()) { return true; } - - auto pos = buf_.find(pattern); - if (pos != std::string::npos) { - if (!content_callback(buf_.data(), pos)) { + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { is_valid_ = false; return false; } - - off_ += pos + pattern.size(); - buf_.erase(0, pos + pattern.size()); - state_ = 4; - } else { - if (!content_callback(buf_.data(), pattern.size())) { - is_valid_ = false; - return false; - } - - off_ += pattern.size(); - buf_.erase(0, pattern.size()); + buf_erase(len); } + return true; } break; } case 4: { // Boundary - if (crlf_.size() > buf_.size()) { return true; } - if (buf_.compare(0, crlf_.size(), crlf_) == 0) { - buf_.erase(0, crlf_.size()); - off_ += crlf_.size(); + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); state_ = 1; } else { - auto pattern = dash_ + crlf_; - if (pattern.size() > buf_.size()) { return true; } - if (buf_.compare(0, pattern.size(), pattern) == 0) { - buf_.erase(0, pattern.size()); - off_ += pattern.size(); + if (dash_crlf_.size() > buf_size()) { return true; } + if (buf_start_with(dash_crlf_)) { + buf_erase(dash_crlf_.size()); is_valid_ = true; - state_ = 5; + buf_erase(buf_size()); // Remove epilogue } else { return true; } } break; } - case 5: { // Done - is_valid_ = false; - return false; - } } } @@ -3061,13 +4132,92 @@ private: file_.content_type.clear(); } - std::string boundary_; + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + const std::string dash_crlf_ = "--\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; - std::string buf_; size_t state_ = 0; bool is_valid_ = false; - size_t off_ = 0; MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; }; inline std::string to_lower(const char *beg, const char *end) { @@ -3084,8 +4234,14 @@ inline std::string make_multipart_data_boundary() { static const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. std::random_device seed_gen; - std::mt19937 engine(seed_gen()); + + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); std::string result = "--cpp-httplib-multipart-data-"; @@ -3096,6 +4252,63 @@ inline std::string make_multipart_data_boundary() { return result; } +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) body += serialize_multipart_formdata_finish(boundary); + + return body; +} + inline std::pair get_range_offset_and_length(const Request &req, size_t content_length, size_t index) { @@ -3108,13 +4321,12 @@ get_range_offset_and_length(const Request &req, size_t content_length, auto slen = static_cast(content_length); if (r.first == -1) { - r.first = slen - r.second; + r.first = (std::max)(static_cast(0), slen - r.second); r.second = slen - 1; } if (r.second == -1) { r.second = slen - 1; } - - return std::make_pair(r.first, r.second - r.first + 1); + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); } inline std::string make_content_range_header_field(size_t offset, size_t length, @@ -3163,21 +4375,21 @@ bool process_multipart_ranges_data(const Request &req, Response &res, return true; } -inline std::string make_multipart_ranges_data(const Request &req, Response &res, - const std::string &boundary, - const std::string &content_type) { - std::string data; - - process_multipart_ranges_data( +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { data += token; }, - [&](const char *token) { data += token; }, + [&](const std::string &token) { data += token; }, [&](size_t offset, size_t length) { - data += res.body.substr(offset, length); - return true; + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; }); - - return data; } inline size_t @@ -3189,7 +4401,7 @@ get_multipart_ranges_data_length(const Request &req, Response &res, process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { data_length += token.size(); }, - [&](const char *token) { data_length += strlen(token); }, + [&](const std::string &token) { data_length += token.size(); }, [&](size_t /*offset*/, size_t length) { data_length += length; return true; @@ -3203,14 +4415,14 @@ inline bool write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type, - T is_shutting_down) { + const T &is_shutting_down) { return process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { strm.write(token); }, - [&](const char *token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, [&](size_t offset, size_t length) { return write_content(strm, res.content_provider_, offset, length, - is_shutting_down) >= 0; + is_shutting_down); }); } @@ -3235,8 +4447,8 @@ inline bool expect_content(const Request &req) { return false; } -inline bool has_crlf(const char *s) { - auto p = s; +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); while (*p) { if (*p == '\r' || *p == '\n') { return true; } p++; @@ -3245,52 +4457,51 @@ inline bool has_crlf(const char *s) { } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -template -inline std::string message_digest(const std::string &s, Init init, - Update update, Final final, - size_t digest_length) { - using namespace std; +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); - std::vector md(digest_length, 0); - CTX ctx; - init(&ctx); - update(&ctx, s.data(), s.size()); - final(md.data(), &ctx); + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; - stringstream ss; - for (auto c : md) { - ss << setfill('0') << setw(2) << hex << (unsigned int)c; + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << (unsigned int)hash[i]; } + return ss.str(); } inline std::string MD5(const std::string &s) { - return message_digest(s, MD5_Init, MD5_Update, MD5_Final, - MD5_DIGEST_LENGTH); + return message_digest(s, EVP_md5()); } inline std::string SHA_256(const std::string &s) { - return message_digest(s, SHA256_Init, SHA256_Update, SHA256_Final, - SHA256_DIGEST_LENGTH); + return message_digest(s, EVP_sha256()); } inline std::string SHA_512(const std::string &s) { - return message_digest(s, SHA512_Init, SHA512_Update, SHA512_Final, - SHA512_DIGEST_LENGTH); + return message_digest(s, EVP_sha512()); } #endif -#ifdef _WIN32 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 // NOTE: This code came up with the following stackoverflow post: // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store inline bool load_system_certs_on_windows(X509_STORE *store) { auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); - if (!hStore) { return false; } + auto result = false; PCCERT_CONTEXT pContext = NULL; - while (pContext = CertEnumCertificatesInStore(hStore, pContext)) { + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { auto encoded_cert = static_cast(pContext->pbCertEncoded); @@ -3298,24 +4509,121 @@ inline bool load_system_certs_on_windows(X509_STORE *store) { if (x509) { X509_STORE_add_cert(store, x509); X509_free(x509); + result = true; } } CertFreeCertificateContext(pContext); CertCloseStore(hStore, 0); + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); return true; } -#endif +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (int i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 class WSInit { public: WSInit() { WSADATA wsaData; - WSAStartup(0x0002, &wsaData); + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; } - ~WSInit() { WSACleanup(); } + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; }; static WSInit wsinit_; @@ -3326,45 +4634,57 @@ inline std::pair make_digest_authentication_header( const Request &req, const std::map &auth, size_t cnonce_count, const std::string &cnonce, const std::string &username, const std::string &password, bool is_proxy = false) { - using namespace std; - - string nc; + std::string nc; { - stringstream ss; - ss << setfill('0') << setw(8) << hex << cnonce_count; + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; nc = ss.str(); } - auto qop = auth.at("qop"); - if (qop.find("auth-int") != std::string::npos) { - qop = "auth-int"; - } else { - qop = "auth"; + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } } std::string algo = "MD5"; if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } - string response; + std::string response; { - auto H = algo == "SHA-256" - ? detail::SHA_256 - : algo == "SHA-512" ? detail::SHA_512 : detail::MD5; + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; auto A1 = username + ":" + auth.at("realm") + ":" + password; auto A2 = req.method + ":" + req.path; if (qop == "auth-int") { A2 += ":" + H(req.body); } - response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + - ":" + qop + ":" + H(A2)); + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } } + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + auto field = "Digest username=\"" + username + "\", realm=\"" + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path + "\", algorithm=" + algo + - ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce + - "\", response=\"" + response + "\""; + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; return std::make_pair(key, field); @@ -3411,7 +4731,7 @@ inline std::string random_string(size_t length) { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; const size_t max_index = (sizeof(charset) - 1); - return charset[static_cast(rand()) % max_index]; + return charset[static_cast(std::rand()) % max_index]; }; std::string str(length, 0); std::generate_n(str.begin(), length, randchar); @@ -3434,6 +4754,53 @@ private: } // namespace detail +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + int dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + // Header utilities inline std::pair make_range_header(Ranges ranges) { std::string field = "bytes="; @@ -3445,16 +4812,15 @@ inline std::pair make_range_header(Ranges ranges) { if (r.second != -1) { field += std::to_string(r.second); } i++; } - return std::make_pair("Range", field); + return std::make_pair("Range", std::move(field)); } inline std::pair make_basic_authentication_header(const std::string &username, - const std::string &password, - bool is_proxy = false) { + const std::string &password, bool is_proxy) { auto field = "Basic " + detail::base64_encode(username + ":" + password); auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - return std::make_pair(key, field); + return std::make_pair(key, std::move(field)); } inline std::pair @@ -3462,45 +4828,37 @@ make_bearer_token_authentication_header(const std::string &token, bool is_proxy = false) { auto field = "Bearer " + token; auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - return std::make_pair(key, field); + return std::make_pair(key, std::move(field)); } // Request implementation -inline bool Request::has_header(const char *key) const { +inline bool Request::has_header(const std::string &key) const { return detail::has_header(headers, key); } -inline std::string Request::get_header_value(const char *key, size_t id) const { +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { return detail::get_header_value(headers, key, id, ""); } -template -inline T Request::get_header_value(const char *key, size_t id) const { - return detail::get_header_value(headers, key, id, 0); -} - -inline size_t Request::get_header_value_count(const char *key) const { +inline size_t Request::get_header_value_count(const std::string &key) const { auto r = headers.equal_range(key); return static_cast(std::distance(r.first, r.second)); } -inline void Request::set_header(const char *key, const char *val) { +inline void Request::set_header(const std::string &key, + const std::string &val) { if (!detail::has_crlf(key) && !detail::has_crlf(val)) { headers.emplace(key, val); } } -inline void Request::set_header(const char *key, const std::string &val) { - if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { - headers.emplace(key, val); - } -} - -inline bool Request::has_param(const char *key) const { +inline bool Request::has_param(const std::string &key) const { return params.find(key) != params.end(); } -inline std::string Request::get_param_value(const char *key, size_t id) const { +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { auto rng = params.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); @@ -3508,59 +4866,59 @@ inline std::string Request::get_param_value(const char *key, size_t id) const { return std::string(); } -inline size_t Request::get_param_value_count(const char *key) const { +inline size_t Request::get_param_value_count(const std::string &key) const { auto r = params.equal_range(key); return static_cast(std::distance(r.first, r.second)); } inline bool Request::is_multipart_form_data() const { const auto &content_type = get_header_value("Content-Type"); - return !content_type.find("multipart/form-data"); + return !content_type.rfind("multipart/form-data", 0); } -inline bool Request::has_file(const char *key) const { +inline bool Request::has_file(const std::string &key) const { return files.find(key) != files.end(); } -inline MultipartFormData Request::get_file_value(const char *key) const { +inline MultipartFormData Request::get_file_value(const std::string &key) const { auto it = files.find(key); if (it != files.end()) { return it->second; } return MultipartFormData(); } +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + // Response implementation -inline bool Response::has_header(const char *key) const { +inline bool Response::has_header(const std::string &key) const { return headers.find(key) != headers.end(); } -inline std::string Response::get_header_value(const char *key, +inline std::string Response::get_header_value(const std::string &key, size_t id) const { return detail::get_header_value(headers, key, id, ""); } -template -inline T Response::get_header_value(const char *key, size_t id) const { - return detail::get_header_value(headers, key, id, 0); -} - -inline size_t Response::get_header_value_count(const char *key) const { +inline size_t Response::get_header_value_count(const std::string &key) const { auto r = headers.equal_range(key); return static_cast(std::distance(r.first, r.second)); } -inline void Response::set_header(const char *key, const char *val) { +inline void Response::set_header(const std::string &key, + const std::string &val) { if (!detail::has_crlf(key) && !detail::has_crlf(val)) { headers.emplace(key, val); } } -inline void Response::set_header(const char *key, const std::string &val) { - if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { - headers.emplace(key, val); - } -} - -inline void Response::set_redirect(const char *url, int stat) { +inline void Response::set_redirect(const std::string &url, int stat) { if (!detail::has_crlf(url)) { set_header("Location", url); if (300 <= stat && stat < 400) { @@ -3571,55 +4929,67 @@ inline void Response::set_redirect(const char *url, int stat) { } } -inline void Response::set_redirect(const std::string &url, int stat) { - set_redirect(url.c_str(), stat); -} - inline void Response::set_content(const char *s, size_t n, - const char *content_type) { + const std::string &content_type) { body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); set_header("Content-Type", content_type); } -inline void Response::set_content(std::string s, const char *content_type) { - body = std::move(s); - set_header("Content-Type", content_type); +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); } -inline void -Response::set_content_provider(size_t in_length, const char *content_type, - ContentProvider provider, - const std::function &resource_releaser) { - assert(in_length > 0); +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = in_length; - content_provider_ = std::move(provider); + if (in_length > 0) { content_provider_ = std::move(provider); } content_provider_resource_releaser_ = resource_releaser; - is_chunked_content_provider = false; + is_chunked_content_provider_ = false; } -inline void -Response::set_content_provider(const char *content_type, - ContentProviderWithoutLength provider, - const std::function &resource_releaser) { +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = 0; content_provider_ = detail::ContentProviderAdapter(std::move(provider)); content_provider_resource_releaser_ = resource_releaser; - is_chunked_content_provider = false; + is_chunked_content_provider_ = false; } inline void Response::set_chunked_content_provider( - const char *content_type, ContentProviderWithoutLength provider, - const std::function &resource_releaser) { + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = 0; content_provider_ = detail::ContentProviderAdapter(std::move(provider)); content_provider_resource_releaser_ = resource_releaser; - is_chunked_content_provider = true; + is_chunked_content_provider_ = true; } -// Rstream implementation +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation inline ssize_t Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); } @@ -3628,40 +4998,6 @@ inline ssize_t Stream::write(const std::string &s) { return write(s.data(), s.size()); } -template -inline ssize_t Stream::write_format(const char *fmt, const Args &... args) { - const auto bufsiz = 2048; - std::array buf; - -#if defined(_MSC_VER) && _MSC_VER < 1900 - auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...); -#else - auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); -#endif - if (sn <= 0) { return sn; } - - auto n = static_cast(sn); - - if (n >= buf.size() - 1) { - std::vector glowable_buf(buf.size()); - - while (n >= glowable_buf.size() - 1) { - glowable_buf.resize(glowable_buf.size() * 2); -#if defined(_MSC_VER) && _MSC_VER < 1900 - n = static_cast(_snprintf_s(&glowable_buf[0], glowable_buf.size(), - glowable_buf.size() - 1, fmt, - args...)); -#else - n = static_cast( - snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); -#endif - } - return write(&glowable_buf[0], n); - } else { - return write(buf.data(), n); - } -} - namespace detail { // Socket stream implementation @@ -3672,7 +5008,7 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, : sock_(sock), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), - write_timeout_usec_(write_timeout_usec) {} + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} inline SocketStream::~SocketStream() {} @@ -3681,33 +5017,65 @@ inline bool SocketStream::is_readable() const { } inline bool SocketStream::is_writable() const { - return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); } inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + if (!is_readable()) { return -1; } -#ifdef _WIN32 - if (size > static_cast((std::numeric_limits::max)())) { - return -1; + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); } - return recv(sock_, ptr, static_cast(size), 0); -#else - return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); }); -#endif } inline ssize_t SocketStream::write(const char *ptr, size_t size) { if (!is_writable()) { return -1; } -#ifdef _WIN32 - if (size > static_cast((std::numeric_limits::max)())) { - return -1; - } - return send(sock_, ptr, static_cast(size), 0); -#else - return handle_EINTR([&]() { return send(sock_, ptr, size, 0); }); +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); #endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); } inline void SocketStream::get_remote_ip_and_port(std::string &ip, @@ -3715,13 +5083,20 @@ inline void SocketStream::get_remote_ip_and_port(std::string &ip, return detail::get_remote_ip_and_port(sock_, ip, port); } +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + // Buffer stream implementation inline bool BufferStream::is_readable() const { return true; } inline bool BufferStream::is_writable() const { return true; } inline ssize_t BufferStream::read(char *ptr, size_t size) { -#if defined(_MSC_VER) && _MSC_VER <= 1900 +#if defined(_MSC_VER) && _MSC_VER < 1910 auto len_read = buffer._Copy_s(ptr, size, size, position); #else auto len_read = buffer.copy(ptr, size, position); @@ -3738,6 +5113,11 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) { inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, int & /*port*/) const {} +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + inline const std::string &BufferStream::get_buffer() const { return buffer; } } // namespace detail @@ -3745,8 +5125,7 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; } // HTTP server implementation inline Server::Server() : new_task_queue( - [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }), - svr_sock_(INVALID_SOCKET), is_running_(false) { + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif @@ -3754,82 +5133,90 @@ inline Server::Server() inline Server::~Server() {} -inline Server &Server::Get(const char *pattern, Handler handler) { - get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Post(const char *pattern, Handler handler) { - post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Post(const char *pattern, +inline Server &Server::Post(const std::string &pattern, HandlerWithContentReader handler) { post_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Put(const char *pattern, Handler handler) { - put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Put(const char *pattern, +inline Server &Server::Put(const std::string &pattern, HandlerWithContentReader handler) { put_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Patch(const char *pattern, Handler handler) { - patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Patch(const char *pattern, +inline Server &Server::Patch(const std::string &pattern, HandlerWithContentReader handler) { patch_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Delete(const char *pattern, Handler handler) { - delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Delete(const char *pattern, +inline Server &Server::Delete(const std::string &pattern, HandlerWithContentReader handler) { delete_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Options(const char *pattern, Handler handler) { - options_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline bool Server::set_base_dir(const char *dir, const char *mount_point) { +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { return set_mount_point(mount_point, dir); } -inline bool Server::set_mount_point(const char *mount_point, const char *dir) { +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { if (detail::is_dir(dir)) { - std::string mnt = mount_point ? mount_point : "/"; + std::string mnt = !mount_point.empty() ? mount_point : "/"; if (!mnt.empty() && mnt[0] == '/') { - base_dirs_.emplace_back(mnt, dir); + base_dirs_.push_back({mnt, dir, std::move(headers)}); return true; } } return false; } -inline bool Server::remove_mount_point(const char *mount_point) { +inline bool Server::remove_mount_point(const std::string &mount_point) { for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { - if (it->first == mount_point) { + if (it->mount_point == mount_point) { base_dirs_.erase(it); return true; } @@ -3837,75 +5224,139 @@ inline bool Server::remove_mount_point(const char *mount_point) { return false; } -inline void Server::set_file_extension_and_mimetype_mapping(const char *ext, - const char *mime) { +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { file_extension_and_mimetype_map_[ext] = mime; + return *this; } -inline void Server::set_file_request_handler(Handler handler) { +inline Server &Server::set_file_request_handler(Handler handler) { file_request_handler_ = std::move(handler); + return *this; } -inline void Server::set_error_handler(Handler handler) { +inline Server &Server::set_error_handler(HandlerWithResponse handler) { error_handler_ = std::move(handler); + return *this; } -inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } - -inline void Server::set_socket_options(SocketOptions socket_options) { - socket_options_ = socket_options; +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; } -inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); } +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} -inline void +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { expect_100_continue_handler_ = std::move(handler); + + return *this; } -inline void Server::set_keep_alive_max_count(size_t count) { +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { keep_alive_max_count_ = count; + return *this; } -inline void Server::set_keep_alive_timeout(time_t sec) { +inline Server &Server::set_keep_alive_timeout(time_t sec) { keep_alive_timeout_sec_ = sec; + return *this; } -inline void Server::set_read_timeout(time_t sec, time_t usec) { +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { read_timeout_sec_ = sec; read_timeout_usec_ = usec; + return *this; } -inline void Server::set_write_timeout(time_t sec, time_t usec) { +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { write_timeout_sec_ = sec; write_timeout_usec_ = usec; + return *this; } -inline void Server::set_idle_interval(time_t sec, time_t usec) { +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { idle_interval_sec_ = sec; idle_interval_usec_ = usec; + return *this; } -inline void Server::set_payload_max_length(size_t length) { +inline Server &Server::set_payload_max_length(size_t length) { payload_max_length_ = length; + return *this; } -inline bool Server::bind_to_port(const char *host, int port, int socket_flags) { +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { if (bind_internal(host, port, socket_flags) < 0) return false; return true; } -inline int Server::bind_to_any_port(const char *host, int socket_flags) { +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { return bind_internal(host, 0, socket_flags); } -inline bool Server::listen_after_bind() { return listen_internal(); } +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} -inline bool Server::listen(const char *host, int port, int socket_flags) { +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); return bind_to_port(host, port, socket_flags) && listen_internal(); } inline bool Server::is_running() const { return is_running_; } +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + inline void Server::stop() { if (is_running_) { assert(svr_sock_ != INVALID_SOCKET); @@ -3916,42 +5367,95 @@ inline void Server::stop() { } inline bool Server::parse_request_line(const char *s, Request &req) { - const static std::regex re( - "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) " - "(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n"); + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; - std::cmatch m; - if (std::regex_match(s, m, re)) { - req.version = std::string(m[5]); - req.method = std::string(m[1]); - req.target = std::string(m[2]); - req.path = detail::decode_url(m[3], false); + { + size_t count = 0; - // Parse query text - auto len = std::distance(m[4].first, m[4].second); - if (len > 0) { detail::parse_query_text(m[4], req.params); } + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); - return true; + if (count != 3) { return false; } } - return false; + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; } inline bool Server::write_response(Stream &strm, bool close_connection, const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { assert(res.status != -1); - if (400 <= res.status && error_handler_) { error_handler_(req, res); } - - detail::BufferStream bstrm; - - // Response line - if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, - detail::status_message(res.status))) { - return false; + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; } - // Headers + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers if (close_connection || req.get_header_value("Connection") == "close") { res.set_header("Connection", "close"); } else { @@ -3966,13 +5470,452 @@ inline bool Server::write_response(Stream &strm, bool close_connection, res.set_header("Content-Type", "text/plain"); } + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { res.set_header("Accept-Ranges", "bytes"); } - std::string content_type; - std::string boundary; + if (post_routing_handler_) { post_routing_handler_(req, res); } + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = 413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + task_queue->enqueue([this, sock]() { process_and_close_socket(sock); }); + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { if (req.ranges.size() > 1) { boundary = detail::make_multipart_data_boundary(); @@ -4008,7 +5951,7 @@ inline bool Server::write_response(Stream &strm, bool close_connection, res.set_header("Content-Length", std::to_string(length)); } else { if (res.content_provider_) { - if (res.is_chunked_content_provider) { + if (res.is_chunked_content_provider_) { res.set_header("Transfer-Encoding", "chunked"); if (type == detail::EncodingType::Gzip) { res.set_header("Content-Encoding", "gzip"); @@ -4016,8 +5959,6 @@ inline bool Server::write_response(Stream &strm, bool close_connection, res.set_header("Content-Encoding", "br"); } } - } else { - res.set_header("Content-Length", "0"); } } } else { @@ -4031,435 +5972,55 @@ inline bool Server::write_response(Stream &strm, bool close_connection, auto content_range = detail::make_content_range_header_field( offset, length, res.body.size()); res.set_header("Content-Range", content_range); - res.body = res.body.substr(offset, length); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } } else { - res.body = - detail::make_multipart_ranges_data(req, res, boundary, content_type); + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } } if (type != detail::EncodingType::None) { - std::shared_ptr compressor; + std::unique_ptr compressor; + std::string content_encoding; if (type == detail::EncodingType::Gzip) { #ifdef CPPHTTPLIB_ZLIB_SUPPORT - compressor = std::make_shared(); - res.set_header("Content-Encoding", "gzip"); + compressor = detail::make_unique(); + content_encoding = "gzip"; #endif } else if (type == detail::EncodingType::Brotli) { #ifdef CPPHTTPLIB_BROTLI_SUPPORT - compressor = std::make_shared(); - res.set_header("Content-Encoding", "brotli"); + compressor = detail::make_unique(); + content_encoding = "br"; #endif } if (compressor) { std::string compressed; - - if (!compressor->compress(res.body.data(), res.body.size(), true, - [&](const char *data, size_t data_len) { - compressed.append(data, data_len); - return true; - })) { - return false; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); } - - res.body.swap(compressed); } } auto length = std::to_string(res.body.size()); res.set_header("Content-Length", length); } - - if (!detail::write_headers(bstrm, res, Headers())) { return false; } - - // Flush buffer - auto &data = bstrm.get_buffer(); - strm.write(data.data(), data.size()); - - // Body - auto ret = true; - if (req.method != "HEAD") { - if (!res.body.empty()) { - if (!strm.write(res.body)) { ret = false; } - } else if (res.content_provider_) { - if (!write_content_with_provider(strm, req, res, boundary, - content_type)) { - ret = false; - } - } - } - - // Log - if (logger_) { logger_(req, res); } - - return ret; -} - -inline bool -Server::write_content_with_provider(Stream &strm, const Request &req, - Response &res, const std::string &boundary, - const std::string &content_type) { - auto is_shutting_down = [this]() { - return this->svr_sock_ == INVALID_SOCKET; - }; - - if (res.content_length_ > 0) { - if (req.ranges.empty()) { - if (detail::write_content(strm, res.content_provider_, 0, - res.content_length_, is_shutting_down) < 0) { - return false; - } - } else if (req.ranges.size() == 1) { - auto offsets = - detail::get_range_offset_and_length(req, res.content_length_, 0); - auto offset = offsets.first; - auto length = offsets.second; - if (detail::write_content(strm, res.content_provider_, offset, length, - is_shutting_down) < 0) { - return false; - } - } else { - if (!detail::write_multipart_ranges_data( - strm, req, res, boundary, content_type, is_shutting_down)) { - return false; - } - } - } else { - if (res.is_chunked_content_provider) { - auto type = detail::encoding_type(req, res); - - std::shared_ptr compressor; - if (type == detail::EncodingType::Gzip) { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - compressor = std::make_shared(); -#endif - } else if (type == detail::EncodingType::Brotli) { -#ifdef CPPHTTPLIB_BROTLI_SUPPORT - compressor = std::make_shared(); -#endif - } else { - compressor = std::make_shared(); - } - assert(compressor != nullptr); - - if (detail::write_content_chunked(strm, res.content_provider_, - is_shutting_down, *compressor) < 0) { - return false; - } - } else { - if (detail::write_content_without_length(strm, res.content_provider_, - is_shutting_down) < 0) { - return false; - } - } - } - return true; -} - -inline bool Server::read_content(Stream &strm, Request &req, Response &res) { - MultipartFormDataMap::iterator cur; - if (read_content_core( - strm, req, res, - // Regular - [&](const char *buf, size_t n) { - if (req.body.size() + n > req.body.max_size()) { return false; } - req.body.append(buf, n); - return true; - }, - // Multipart - [&](const MultipartFormData &file) { - cur = req.files.emplace(file.name, file); - return true; - }, - [&](const char *buf, size_t n) { - auto &content = cur->second.content; - if (content.size() + n > content.max_size()) { return false; } - content.append(buf, n); - return true; - })) { - const auto &content_type = req.get_header_value("Content-Type"); - if (!content_type.find("application/x-www-form-urlencoded")) { - detail::parse_query_text(req.body, req.params); - } - return true; - } - return false; -} - -inline bool Server::read_content_with_content_receiver( - Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver) { - return read_content_core(strm, req, res, receiver, multipart_header, - multipart_receiver); -} - -inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, - ContentReceiver receiver, - MultipartContentHeader mulitpart_header, - ContentReceiver multipart_receiver) { - detail::MultipartFormDataParser multipart_form_data_parser; - ContentReceiver out; - - if (req.is_multipart_form_data()) { - const auto &content_type = req.get_header_value("Content-Type"); - std::string boundary; - if (!detail::parse_multipart_boundary(content_type, boundary)) { - res.status = 400; - return false; - } - - multipart_form_data_parser.set_boundary(std::move(boundary)); - out = [&](const char *buf, size_t n) { - /* For debug - size_t pos = 0; - while (pos < n) { - auto read_size = std::min(1, n - pos); - auto ret = multipart_form_data_parser.parse( - buf + pos, read_size, multipart_receiver, mulitpart_header); - if (!ret) { return false; } - pos += read_size; - } - return true; - */ - return multipart_form_data_parser.parse(buf, n, multipart_receiver, - mulitpart_header); - }; - } else { - out = receiver; - } - - if (req.method == "DELETE" && !req.has_header("Content-Length")) { - return true; - } - - if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, - out, true)) { - return false; - } - - if (req.is_multipart_form_data()) { - if (!multipart_form_data_parser.is_valid()) { - res.status = 400; - return false; - } - } - - return true; -} - -inline bool Server::handle_file_request(Request &req, Response &res, - bool head) { - for (const auto &kv : base_dirs_) { - const auto &mount_point = kv.first; - const auto &base_dir = kv.second; - - // Prefix match - if (!req.path.compare(0, mount_point.size(), mount_point)) { - std::string sub_path = "/" + req.path.substr(mount_point.size()); - if (detail::is_valid_path(sub_path)) { - auto path = base_dir + sub_path; - if (path.back() == '/') { path += "index.html"; } - - if (detail::is_file(path)) { - detail::read_file(path, res.body); - auto type = - detail::find_content_type(path, file_extension_and_mimetype_map_); - if (type) { res.set_header("Content-Type", type); } - res.status = 200; - if (!head && file_request_handler_) { - file_request_handler_(req, res); - } - return true; - } - } - } - } - return false; -} - -inline socket_t -Server::create_server_socket(const char *host, int port, int socket_flags, - SocketOptions socket_options) const { - return detail::create_socket( - host, port, socket_flags, tcp_nodelay_, socket_options, - [](socket_t sock, struct addrinfo &ai) -> bool { - if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { - return false; - } - if (::listen(sock, 5)) { // Listen through 5 channels - return false; - } - return true; - }); -} - -inline int Server::bind_internal(const char *host, int port, int socket_flags) { - if (!is_valid()) { return -1; } - - svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); - if (svr_sock_ == INVALID_SOCKET) { return -1; } - - if (port == 0) { - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - if (getsockname(svr_sock_, reinterpret_cast(&addr), - &addr_len) == -1) { - return -1; - } - if (addr.ss_family == AF_INET) { - return ntohs(reinterpret_cast(&addr)->sin_port); - } else if (addr.ss_family == AF_INET6) { - return ntohs(reinterpret_cast(&addr)->sin6_port); - } else { - return -1; - } - } else { - return port; - } -} - -inline bool Server::listen_internal() { - auto ret = true; - is_running_ = true; - - { - std::unique_ptr task_queue(new_task_queue()); - - while (svr_sock_ != INVALID_SOCKET) { -#ifndef _WIN32 - if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { -#endif - auto val = detail::select_read(svr_sock_, idle_interval_sec_, - idle_interval_usec_); - if (val == 0) { // Timeout - task_queue->on_idle(); - continue; - } -#ifndef _WIN32 - } -#endif - socket_t sock = accept(svr_sock_, nullptr, nullptr); - - if (sock == INVALID_SOCKET) { - if (errno == EMFILE) { - // The per-process limit of open file descriptors has been reached. - // Try to accept new connections after a short sleep. - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - if (svr_sock_ != INVALID_SOCKET) { - detail::close_socket(svr_sock_); - ret = false; - } else { - ; // The server socket was closed by user. - } - break; - } - -#if __cplusplus > 201703L - task_queue->enqueue([=, this]() { process_and_close_socket(sock); }); -#else - task_queue->enqueue([=]() { process_and_close_socket(sock); }); -#endif - } - - task_queue->shutdown(); - } - - is_running_ = false; - return ret; -} - -inline bool Server::routing(Request &req, Response &res, Stream &strm) { - // File handler - bool is_head_request = req.method == "HEAD"; - if ((req.method == "GET" || is_head_request) && - handle_file_request(req, res, is_head_request)) { - return true; - } - - if (detail::expect_content(req)) { - // Content reader handler - { - ContentReader reader( - [&](ContentReceiver receiver) { - return read_content_with_content_receiver(strm, req, res, receiver, - nullptr, nullptr); - }, - [&](MultipartContentHeader header, ContentReceiver receiver) { - return read_content_with_content_receiver(strm, req, res, nullptr, - header, receiver); - }); - - if (req.method == "POST") { - if (dispatch_request_for_content_reader( - req, res, reader, post_handlers_for_content_reader_)) { - return true; - } - } else if (req.method == "PUT") { - if (dispatch_request_for_content_reader( - req, res, reader, put_handlers_for_content_reader_)) { - return true; - } - } else if (req.method == "PATCH") { - if (dispatch_request_for_content_reader( - req, res, reader, patch_handlers_for_content_reader_)) { - return true; - } - } else if (req.method == "DELETE") { - if (dispatch_request_for_content_reader( - req, res, reader, delete_handlers_for_content_reader_)) { - return true; - } - } - } - - // Read content into `req.body` - if (!read_content(strm, req, res)) { return false; } - } - - // Regular handler - if (req.method == "GET" || req.method == "HEAD") { - return dispatch_request(req, res, get_handlers_); - } else if (req.method == "POST") { - return dispatch_request(req, res, post_handlers_); - } else if (req.method == "PUT") { - return dispatch_request(req, res, put_handlers_); - } else if (req.method == "DELETE") { - return dispatch_request(req, res, delete_handlers_); - } else if (req.method == "OPTIONS") { - return dispatch_request(req, res, options_handlers_); - } else if (req.method == "PATCH") { - return dispatch_request(req, res, patch_handlers_); - } - - res.status = 400; - return false; -} - -inline bool Server::dispatch_request(Request &req, Response &res, - const Handlers &handlers) { - - try { - for (const auto &x : handlers) { - const auto &pattern = x.first; - const auto &handler = x.second; - - if (std::regex_match(req.path, req.matches, pattern)) { - handler(req, res); - return true; - } - } - } catch (const std::exception &ex) { - res.status = 500; - res.set_header("EXCEPTION_WHAT", ex.what()); - } catch (...) { - res.status = 500; - res.set_header("EXCEPTION_WHAT", "UNKNOWN"); - } - return false; } inline bool Server::dispatch_request_for_content_reader( @@ -4493,6 +6054,26 @@ Server::process_request(Stream &strm, bool close_connection, res.version = "HTTP/1.1"; + for (const auto &header : default_headers_) { + if (res.headers.find(header.first) == res.headers.end()) { + res.headers.insert(header); + } + } + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + // Check if the request URI doesn't exceed the limit if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { Headers dummy; @@ -4521,10 +6102,15 @@ Server::process_request(Stream &strm, bool close_connection, req.set_header("REMOTE_ADDR", req.remote_addr); req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + if (req.has_header("Range")) { const auto &range_header_value = req.get_header_value("Range"); if (!detail::parse_range_header(range_header_value, req.ranges)) { - // TODO: error + res.status = 416; + return write_response(strm, close_connection, req, res); } } @@ -4546,21 +6132,58 @@ Server::process_request(Stream &strm, bool close_connection, } // Rounting - if (routing(req, res, strm)) { + bool routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + + if (routed) { if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); } else { if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); } - - return write_response(strm, close_connection, req, res); } inline bool Server::is_valid() const { return true; } inline bool Server::process_and_close_socket(socket_t sock) { auto ret = detail::process_server_socket( - sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, - read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, [this](Stream &strm, bool close_connection, bool &connection_closed) { return process_request(strm, close_connection, connection_closed, nullptr); @@ -4582,15 +6205,17 @@ inline ClientImpl::ClientImpl(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) : host_(host), port_(port), - host_and_port_(host_ + ":" + std::to_string(port_)), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} -inline ClientImpl::~ClientImpl() { stop_core(); } +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} inline bool ClientImpl::is_valid() const { return true; } -inline Error ClientImpl::get_last_error() const { return error_; } - inline void ClientImpl::copy_settings(const ClientImpl &rhs) { client_cert_path_ = rhs.client_cert_path_; client_key_path_ = rhs.client_key_path_; @@ -4608,6 +6233,8 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) { #endif keep_alive_ = rhs.keep_alive_; follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; tcp_nodelay_ = rhs.tcp_nodelay_; socket_options_ = rhs.socket_options_; compress_ = rhs.compress_; @@ -4622,50 +6249,96 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) { proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; #endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT server_certificate_verification_ = rhs.server_certificate_verification_; #endif logger_ = rhs.logger_; } -inline socket_t ClientImpl::create_client_socket() const { +inline socket_t ClientImpl::create_client_socket(Error &error) const { if (!proxy_host_.empty() && proxy_port_ != -1) { return detail::create_client_socket( - proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_, - connection_timeout_sec_, connection_timeout_usec_, interface_, error_); + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) ip = it->second; + return detail::create_client_socket( - host_.c_str(), port_, tcp_nodelay_, socket_options_, - connection_timeout_sec_, connection_timeout_usec_, interface_, error_); + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); } -inline bool ClientImpl::create_and_connect_socket(Socket &socket) { - auto sock = create_client_socket(); +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); if (sock == INVALID_SOCKET) { return false; } socket.sock = sock; return true; } -inline void ClientImpl::close_socket(Socket &socket, - bool /*process_socket_ret*/) { - detail::close_socket(socket.sock); - socket_.sock = INVALID_SOCKET; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - socket_.ssl = nullptr; -#endif +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); } -inline bool ClientImpl::read_response_line(Stream &strm, Response &res) { - std::array buf; +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf{}; detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); if (!line_reader.getline()) { return false; } - const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n"); +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#endif std::cmatch m; - if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } res.version = std::string(m[1]); res.status = std::stoi(std::string(m[2])); res.reason = std::string(m[3]); @@ -4684,74 +6357,144 @@ inline bool ClientImpl::read_response_line(Stream &strm, Response &res) { return true; } -inline bool ClientImpl::send(const Request &req, Response &res) { +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { { std::lock_guard guard(socket_mutex_); + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + auto is_alive = false; if (socket_.is_open()) { - is_alive = detail::select_write(socket_.sock, 0, 0) > 0; - if (!is_alive) { close_socket(socket_, false); } + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } } if (!is_alive) { - if (!create_and_connect_socket(socket_)) { return false; } + if (!create_and_connect_socket(socket_, error)) { return false; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT // TODO: refactoring if (is_ssl()) { auto &scli = static_cast(*this); if (!proxy_host_.empty() && proxy_port_ != -1) { - bool success = false; - if (!scli.connect_with_proxy(socket_, res, success)) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { return success; } } - if (!scli.initialize_ssl(socket_)) { return false; } + if (!scli.initialize_ssl(socket_, error)) { return false; } } #endif } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); } + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; auto close_connection = !keep_alive_; - auto ret = process_socket(socket_, [&](Stream &strm) { - return handle_request(strm, req, res, close_connection); + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } }); - if (close_connection || !ret) { stop_core(); } + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); if (!ret) { - if (error_ == Error::Success) { error_ = Error::Unknown; } + if (error == Error::Success) { error = Error::Unknown; } } return ret; } -inline bool ClientImpl::handle_request(Stream &strm, const Request &req, - Response &res, bool close_connection) { +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { if (req.path.empty()) { - error_ = Error::Connection; + error = Error::Connection; return false; } + auto req_save = req; + bool ret; if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { auto req2 = req; req2.path = "http://" + host_and_port_ + req.path; - ret = process_request(strm, req2, res, close_connection); + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; } else { - ret = process_request(strm, req, res, close_connection); + ret = process_request(strm, req, res, close_connection, error); } if (!ret) { return false; } if (300 < res.status && res.status < 400 && follow_location_) { - ret = redirect(req, res); + req = req_save; + ret = redirect(req, res, error); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -4768,15 +6511,15 @@ inline bool ClientImpl::handle_request(Stream &strm, const Request &req, if (detail::parse_www_authenticate(res, auth, is_proxy)) { Request new_req = req; new_req.authorization_count_ += 1; - auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - new_req.headers.erase(key); + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); new_req.headers.insert(detail::make_digest_authentication_header( req, auth, new_req.authorization_count_, detail::random_string(10), username, password, is_proxy)); Response new_res; - ret = send(new_req, new_res); + ret = send(new_req, new_res, error); if (ret) { res = new_res; } } } @@ -4786,17 +6529,17 @@ inline bool ClientImpl::handle_request(Stream &strm, const Request &req, return ret; } -inline bool ClientImpl::redirect(const Request &req, Response &res) { - if (req.redirect_count == 0) { - error_ = Error::ExceedRedirectCount; +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; return false; } - auto location = detail::decode_url(res.get_header_value("location"), true); + auto location = res.get_header_value("location"); if (location.empty()) { return false; } const static std::regex re( - R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); std::smatch m; if (!std::regex_match(location, m, re)) { return false; } @@ -4805,8 +6548,10 @@ inline bool ClientImpl::redirect(const Request &req, Response &res) { auto next_scheme = m[1].str(); auto next_host = m[2].str(); - auto port_str = m[3].str(); - auto next_path = m[4].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); auto next_port = port_; if (!port_str.empty()) { @@ -4819,179 +6564,202 @@ inline bool ClientImpl::redirect(const Request &req, Response &res) { if (next_host.empty()) { next_host = host_; } if (next_path.empty()) { next_path = "/"; } + auto path = detail::decode_url(next_path, true) + next_query; + if (next_scheme == scheme && next_host == host_ && next_port == port_) { - return detail::redirect(*this, req, res, next_path); + return detail::redirect(*this, req, res, path, location, error); } else { if (next_scheme == "https") { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT SSLClient cli(next_host.c_str(), next_port); cli.copy_settings(*this); - auto ret = detail::redirect(cli, req, res, next_path); - if (!ret) { error_ = cli.get_last_error(); } - return ret; + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); #else return false; #endif } else { ClientImpl cli(next_host.c_str(), next_port); cli.copy_settings(*this); - auto ret = detail::redirect(cli, req, res, next_path); - if (!ret) { error_ = cli.get_last_error(); } - return ret; + return detail::redirect(cli, req, res, path, location, error); } } } -inline bool ClientImpl::write_request(Stream &strm, const Request &req, - bool close_connection) { - detail::BufferStream bstrm; +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; - // Request line - const auto &path = detail::encode_url(req.path); + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } - bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} - // Additonal headers - Headers headers; - if (close_connection) { headers.emplace("Connection", "close"); } +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.headers.emplace("Connection", "close"); + } + } if (!req.has_header("Host")) { if (is_ssl()) { if (port_ == 443) { - headers.emplace("Host", host_); + req.headers.emplace("Host", host_); } else { - headers.emplace("Host", host_and_port_); + req.headers.emplace("Host", host_and_port_); } } else { if (port_ == 80) { - headers.emplace("Host", host_); + req.headers.emplace("Host", host_); } else { - headers.emplace("Host", host_and_port_); + req.headers.emplace("Host", host_and_port_); } } } - if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); } + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT if (!req.has_header("User-Agent")) { - headers.emplace("User-Agent", "cpp-httplib/0.7"); + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.headers.emplace("User-Agent", agent); } +#endif if (req.body.empty()) { - if (req.content_provider) { - auto length = std::to_string(req.content_length); - headers.emplace("Content-Length", length); + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } } else { - headers.emplace("Content-Length", "0"); + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } } } else { if (!req.has_header("Content-Type")) { - headers.emplace("Content-Type", "text/plain"); + req.headers.emplace("Content-Type", "text/plain"); } if (!req.has_header("Content-Length")) { auto length = std::to_string(req.body.size()); - headers.emplace("Content-Length", length); + req.headers.emplace("Content-Length", length); } } - if (!basic_auth_password_.empty()) { - headers.insert(make_basic_authentication_header( - basic_auth_username_, basic_auth_password_, false)); + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } } if (!proxy_basic_auth_username_.empty() && !proxy_basic_auth_password_.empty()) { - headers.insert(make_basic_authentication_header( - proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } } if (!bearer_token_auth_token_.empty()) { - headers.insert(make_bearer_token_authentication_header( - bearer_token_auth_token_, false)); + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } } if (!proxy_bearer_token_auth_token_.empty()) { - headers.insert(make_bearer_token_authentication_header( - proxy_bearer_token_auth_token_, true)); + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } } - detail::write_headers(bstrm, req, headers); + // Request line and headers + { + detail::BufferStream bstrm; - // Flush buffer - auto &data = bstrm.get_buffer(); - if (!detail::write_data(strm, data.data(), data.size())) { - error_ = Error::Write; - return false; + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } } // Body if (req.body.empty()) { - if (req.content_provider) { - size_t offset = 0; - size_t end_offset = req.content_length; + return write_content_with_provider(strm, req, error); + } - bool ok = true; - - DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { - if (ok) { - if (detail::write_data(strm, d, l)) { - offset += l; - } else { - ok = false; - } - } - }; - data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; - - while (offset < end_offset) { - if (!req.content_provider(offset, end_offset - offset, data_sink)) { - error_ = Error::Canceled; - return false; - } - if (!ok) { - error_ = Error::Write; - return false; - } - } - } - } else { - return detail::write_data(strm, req.body.data(), req.body.size()); + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; } return true; } -inline std::shared_ptr ClientImpl::send_with_content_provider( - const char *method, const char *path, const Headers &headers, - const std::string &body, size_t content_length, - ContentProvider content_provider, const char *content_type) { - - Request req; - req.method = method; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); - req.path = path; - - if (content_type) { req.headers.emplace("Content-Type", content_type); } +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } #ifdef CPPHTTPLIB_ZLIB_SUPPORT - if (compress_) { + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support detail::gzip_compressor compressor; if (content_provider) { auto ok = true; size_t offset = 0; - DataSink data_sink; - data_sink.write = [&](const char *data, size_t data_len) { + + data_sink.write = [&](const char *data, size_t data_len) -> bool { if (ok) { auto last = offset + data_len == content_length; auto ret = compressor.compress( - data, data_len, last, [&](const char *data, size_t data_len) { - req.body.append(data, data_len); + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); return true; }); @@ -5001,94 +6769,162 @@ inline std::shared_ptr ClientImpl::send_with_content_provider( ok = false; } } + return ok; }; - data_sink.is_writable = [&](void) { return ok && true; }; while (ok && offset < content_length) { if (!content_provider(offset, content_length - offset, data_sink)) { - error_ = Error::Canceled; + error = Error::Canceled; return nullptr; } } } else { - if (!compressor.compress(body.data(), body.size(), true, + if (!compressor.compress(body, content_length, true, [&](const char *data, size_t data_len) { req.body.append(data, data_len); return true; })) { + error = Error::Compression; return nullptr; } } - - req.headers.emplace("Content-Encoding", "gzip"); } else #endif { if (content_provider) { - req.content_length = content_length; - req.content_provider = content_provider; + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); } else { - req.body = body; + req.body.assign(body, content_length); + ; } } - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; } -inline bool ClientImpl::process_request(Stream &strm, const Request &req, - Response &res, bool close_connection) { +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { // Send request - if (!write_request(strm, req, close_connection)) { return false; } + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif // Receive response and headers - if (!read_response_line(strm, res) || + if (!read_response_line(strm, req, res) || !detail::read_headers(strm, res.headers)) { - error_ = Error::Read; + error = Error::Read; return false; } - if (req.response_handler) { - if (!req.response_handler(res)) { - error_ = Error::Canceled; - return false; - } - } - // Body - if (req.method != "HEAD" && req.method != "CONNECT") { + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + auto out = req.content_receiver - ? static_cast([&](const char *buf, size_t n) { - auto ret = req.content_receiver(buf, n); - if (!ret) { error_ = Error::Canceled; } - return ret; - }) - : static_cast([&](const char *buf, size_t n) { - if (res.body.size() + n > res.body.max_size()) { return false; } - res.body.append(buf, n); - return true; - }); + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); auto progress = [&](uint64_t current, uint64_t total) { - if (!req.progress) { return true; } + if (!req.progress || redirect) { return true; } auto ret = req.progress(current, total); - if (!ret) { error_ = Error::Canceled; } + if (!ret) { error = Error::Canceled; } return ret; }; int dummy_status; if (!detail::read_content(strm, res, (std::numeric_limits::max)(), - dummy_status, progress, out, decompress_)) { - if (error_ != Error::Canceled) { error_ = Error::Read; } + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } return false; } } if (res.get_header_value("Connection") == "close" || (res.version == "HTTP/1.0" && res.reason != "Connection established")) { - stop_core(); + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); } // Log @@ -5097,312 +6933,529 @@ inline bool ClientImpl::process_request(Stream &strm, const Request &req, return true; } +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + size_t cur_item = 0, cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && items.size()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + bool has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + inline bool -ClientImpl::process_socket(Socket &socket, +ClientImpl::process_socket(const Socket &socket, std::function callback) { - return detail::process_client_socket(socket.sock, read_timeout_sec_, - read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, callback); + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); } inline bool ClientImpl::is_ssl() const { return false; } -inline Result ClientImpl::Get(const char *path) { +inline Result ClientImpl::Get(const std::string &path) { return Get(path, Headers(), Progress()); } -inline Result ClientImpl::Get(const char *path, Progress progress) { +inline Result ClientImpl::Get(const std::string &path, Progress progress) { return Get(path, Headers(), std::move(progress)); } -inline Result ClientImpl::Get(const char *path, const Headers &headers) { +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { return Get(path, headers, Progress()); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, Progress progress) { Request req; req.method = "GET"; req.path = path; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.progress = std::move(progress); - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } -inline Result ClientImpl::Get(const char *path, +inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver) { return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const char *path, +inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver, Progress progress) { return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress)); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver) { return Get(path, headers, nullptr, std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, Progress progress) { return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress)); } -inline Result ClientImpl::Get(const char *path, +inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver) { - return Get(path, Headers(), std::move(response_handler), content_receiver, - nullptr); + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver) { - return Get(path, headers, std::move(response_handler), content_receiver, - nullptr); + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const char *path, +inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { - return Get(path, Headers(), std::move(response_handler), content_receiver, - progress); + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); } -inline Result ClientImpl::Get(const char *path, const Headers &headers, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { Request req; req.method = "GET"; req.path = path; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.response_handler = std::move(response_handler); - req.content_receiver = std::move(content_receiver); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; req.progress = std::move(progress); - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } -inline Result ClientImpl::Head(const char *path) { +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const std::string &path) { return Head(path, Headers()); } -inline Result ClientImpl::Head(const char *path, const Headers &headers) { +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { Request req; req.method = "HEAD"; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.path = path; - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } -inline Result ClientImpl::Post(const char *path) { - return Post(path, std::string(), nullptr); +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); } -inline Result ClientImpl::Post(const char *path, const std::string &body, - const char *content_type) { +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { return Post(path, Headers(), body, content_type); } -inline Result ClientImpl::Post(const char *path, const Headers &headers, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const std::string &body, - const char *content_type) { - auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Post(const char *path, const Params ¶ms) { +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { return Post(path, Headers(), params); } -inline Result ClientImpl::Post(const char *path, size_t content_length, +inline Result ClientImpl::Post(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return Post(path, Headers(), content_length, content_provider, content_type); + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Post(const char *path, const Headers &headers, +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const char *content_type) { - auto ret = send_with_content_provider("POST", path, headers, std::string(), - content_length, content_provider, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); } -inline Result ClientImpl::Post(const char *path, const Headers &headers, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const Params ¶ms) { auto query = detail::params_to_query_str(params); return Post(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Post(const char *path, +inline Result ClientImpl::Post(const std::string &path, const MultipartFormDataItems &items) { return Post(path, Headers(), items); } -inline Result ClientImpl::Post(const char *path, const Headers &headers, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items) { - auto boundary = detail::make_multipart_data_boundary(); - - std::string body; - - for (const auto &item : items) { - body += "--" + boundary + "\r\n"; - body += "Content-Disposition: form-data; name=\"" + item.name + "\""; - if (!item.filename.empty()) { - body += "; filename=\"" + item.filename + "\""; - } - body += "\r\n"; - if (!item.content_type.empty()) { - body += "Content-Type: " + item.content_type + "\r\n"; - } - body += "\r\n"; - body += item.content + "\r\n"; - } - - body += "--" + boundary + "--\r\n"; - - std::string content_type = "multipart/form-data; boundary=" + boundary; + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); return Post(path, headers, body, content_type.c_str()); } -inline Result ClientImpl::Put(const char *path) { - return Put(path, std::string(), nullptr); +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); } -inline Result ClientImpl::Put(const char *path, const std::string &body, - const char *content_type) { +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { return Put(path, Headers(), body, content_type); } -inline Result ClientImpl::Put(const char *path, const Headers &headers, +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const std::string &body, - const char *content_type) { - auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Put(const char *path, size_t content_length, +inline Result ClientImpl::Put(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return Put(path, Headers(), content_length, content_provider, content_type); + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Put(const char *path, const Headers &headers, +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const char *content_type) { - auto ret = send_with_content_provider("PUT", path, headers, std::string(), - content_length, content_provider, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); } -inline Result ClientImpl::Put(const char *path, const Params ¶ms) { +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { return Put(path, Headers(), params); } -inline Result ClientImpl::Put(const char *path, const Headers &headers, +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const Params ¶ms) { auto query = detail::params_to_query_str(params); return Put(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Patch(const char *path, const std::string &body, - const char *content_type) { +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { return Patch(path, Headers(), body, content_type); } -inline Result ClientImpl::Patch(const char *path, const Headers &headers, +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const std::string &body, - const char *content_type) { - auto ret = send_with_content_provider("PATCH", path, headers, body, 0, - nullptr, content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Patch(const char *path, size_t content_length, +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return Patch(path, Headers(), content_length, content_provider, content_type); + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Patch(const char *path, const Headers &headers, +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const char *content_type) { - auto ret = send_with_content_provider("PATCH", path, headers, std::string(), - content_length, content_provider, - content_type); - return Result{ret, get_last_error()}; + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); } -inline Result ClientImpl::Delete(const char *path) { - return Delete(path, Headers(), std::string(), nullptr); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); } -inline Result ClientImpl::Delete(const char *path, const std::string &body, - const char *content_type) { - return Delete(path, Headers(), body, content_type); +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); } -inline Result ClientImpl::Delete(const char *path, const Headers &headers) { - return Delete(path, headers, std::string(), nullptr); +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); } -inline Result ClientImpl::Delete(const char *path, const Headers &headers, - const std::string &body, - const char *content_type) { +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { Request req; req.method = "DELETE"; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.path = path; - if (content_type) { req.headers.emplace("Content-Type", content_type); } - req.body = body; + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + req.body.assign(body, content_length); - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } -inline Result ClientImpl::Options(const char *path) { +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { return Options(path, Headers()); } -inline Result ClientImpl::Options(const char *path, const Headers &headers) { +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { Request req; req.method = "OPTIONS"; - req.headers = default_headers_; - req.headers.insert(headers.begin(), headers.end()); + req.headers = headers; req.path = path; - auto res = std::make_shared(); - auto ret = send(req, *res); - return Result{ret ? res : nullptr, get_last_error()}; + return send_(std::move(req)); } inline size_t ClientImpl::is_socket_open() const { @@ -5410,19 +7463,29 @@ inline size_t ClientImpl::is_socket_open() const { return socket_.is_open(); } -inline void ClientImpl::stop() { - stop_core(); - error_ = Error::Canceled; -} +inline socket_t ClientImpl::socket() const { return socket_.sock; } -inline void ClientImpl::stop_core() { +inline void ClientImpl::stop() { std::lock_guard guard(socket_mutex_); - if (socket_.is_open()) { - detail::shutdown_socket(socket_.sock); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - close_socket(socket_, true); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); } inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { @@ -5440,19 +7503,19 @@ inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { write_timeout_usec_ = usec; } -inline void ClientImpl::set_basic_auth(const char *username, - const char *password) { +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { basic_auth_username_ = username; basic_auth_password_ = password; } -inline void ClientImpl::set_bearer_token_auth(const char *token) { +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { bearer_token_auth_token_ = token; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_digest_auth(const char *username, - const char *password) { +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { digest_auth_username_ = username; digest_auth_password_ = password; } @@ -5462,45 +7525,72 @@ inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + inline void ClientImpl::set_default_headers(Headers headers) { default_headers_ = std::move(headers); } +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } inline void ClientImpl::set_socket_options(SocketOptions socket_options) { - socket_options_ = socket_options; + socket_options_ = std::move(socket_options); } inline void ClientImpl::set_compress(bool on) { compress_ = on; } inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } -inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; } +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} -inline void ClientImpl::set_proxy(const char *host, int port) { +inline void ClientImpl::set_proxy(const std::string &host, int port) { proxy_host_ = host; proxy_port_ = port; } -inline void ClientImpl::set_proxy_basic_auth(const char *username, - const char *password) { +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { proxy_basic_auth_username_ = username; proxy_basic_auth_password_ = password; } -inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) { +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { proxy_bearer_token_auth_token_ = token; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_proxy_digest_auth(const char *username, - const char *password) { +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { proxy_digest_auth_username_ = username; proxy_digest_auth_password_ = password; } #endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} +#endif + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT inline void ClientImpl::enable_server_certificate_verification(bool enabled) { server_certificate_verification_ = enabled; @@ -5527,7 +7617,9 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, } if (ssl) { + set_nonblocking(sock, true); auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); SSL_set_bio(ssl, bio, bio); if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { @@ -5536,32 +7628,58 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, std::lock_guard guard(ctx_mutex); SSL_free(ssl); } + set_nonblocking(sock, false); return nullptr; } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); } return ssl; } inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, - bool process_socket_ret) { - if (process_socket_ret) { - SSL_shutdown(ssl); // shutdown only if not already closed by remote - } + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } std::lock_guard guard(ctx_mutex); SSL_free(ssl); } +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + template -inline bool -process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count, - time_t keep_alive_timeout_sec, - time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec, - T callback) { +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { return process_server_socket_core( - sock, keep_alive_max_count, keep_alive_timeout_sec, + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, [&](bool close_connection, bool &connection_closed) { SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); @@ -5579,55 +7697,12 @@ process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, return callback(strm); } -#if OPENSSL_VERSION_NUMBER < 0x10100000L -static std::shared_ptr> openSSL_locks_; - -class SSLThreadLocks { -public: - SSLThreadLocks() { - openSSL_locks_ = - std::make_shared>(CRYPTO_num_locks()); - CRYPTO_set_locking_callback(locking_callback); - } - - ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); } - -private: - static void locking_callback(int mode, int type, const char * /*file*/, - int /*line*/) { - auto &lk = (*openSSL_locks_)[static_cast(type)]; - if (mode & CRYPTO_LOCK) { - lk.lock(); - } else { - lk.unlock(); - } - } -}; - -#endif - class SSLInit { public: SSLInit() { -#if OPENSSL_VERSION_NUMBER < 0x1010001fL - SSL_load_error_strings(); - SSL_library_init(); -#else OPENSSL_init_ssl( OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); -#endif } - - ~SSLInit() { -#if OPENSSL_VERSION_NUMBER < 0x1010001fL - ERR_free_strings(); -#endif - } - -private: -#if OPENSSL_VERSION_NUMBER < 0x10100000L - SSLThreadLocks thread_init_; -#endif }; // SSL socket stream implementation @@ -5640,22 +7715,7 @@ inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), write_timeout_usec_(write_timeout_usec) { - { - timeval tv; - tv.tv_sec = static_cast(read_timeout_sec); - tv.tv_usec = static_cast(read_timeout_usec); - - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), - sizeof(tv)); - } - { - timeval tv; - tv.tv_sec = static_cast(write_timeout_sec); - tv.tv_usec = static_cast(write_timeout_usec); - - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&tv), - sizeof(tv)); - } + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); } inline SSLSocketStream::~SSLSocketStream() {} @@ -5665,19 +7725,70 @@ inline bool SSLSocketStream::is_readable() const { } inline bool SSLSocketStream::is_writable() const { - return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) > - 0; + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); } inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { - if (SSL_pending(ssl_) > 0 || is_readable()) { + if (SSL_pending(ssl_) > 0) { return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; } return -1; } inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { - if (is_writable()) { return SSL_write(ssl_, ptr, static_cast(size)); } + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } return -1; } @@ -5686,6 +7797,13 @@ inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, detail::get_remote_ip_and_port(sock_, ip, port); } +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + static SSLInit sslinit_; } // namespace detail @@ -5693,18 +7811,22 @@ static SSLInit sslinit_; // SSL HTTP server implementation inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path, - const char *client_ca_cert_dir_path) { - ctx_ = SSL_CTX_new(SSLv23_server_method()); + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); if (ctx_) { SSL_CTX_set_options(ctx_, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | + SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - // SSL_CTX_set_tmp_ecdh(ctx_, ecdh); - // EC_KEY_free(ecdh); + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + // add default password callback before opening encrypted private key + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata(ctx_, + (char *)private_key_password); + } if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != @@ -5712,46 +7834,46 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, SSL_CTX_free(ctx_); ctx_ = nullptr; } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { - // if (client_ca_cert_file_path) { - // auto list = SSL_load_client_CA_file(client_ca_cert_file_path); - // SSL_CTX_set_client_CA_list(ctx_, list); - // } - SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, client_ca_cert_dir_path); SSL_CTX_set_verify( - ctx_, - SSL_VERIFY_PEER | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, - nullptr); + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); } } } inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store) { - ctx_ = SSL_CTX_new(SSLv23_server_method()); + ctx_ = SSL_CTX_new(TLS_server_method()); if (ctx_) { SSL_CTX_set_options(ctx_, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | + SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } else if (client_ca_cert_store) { - SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); SSL_CTX_set_verify( - ctx_, - SSL_VERIFY_PEER | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, - nullptr); + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; } } } @@ -5762,13 +7884,21 @@ inline SSLServer::~SSLServer() { inline bool SSLServer::is_valid() const { return ctx_; } -inline bool SSLServer::process_and_close_socket(socket_t sock) { - auto ssl = detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept, - [](SSL * /*ssl*/) { return true; }); +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; if (ssl) { - auto ret = detail::process_server_socket_ssl( - ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, [this, ssl](Stream &strm, bool close_connection, @@ -5777,12 +7907,15 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) { [&](Request &req) { req.ssl = ssl; }); }); - detail::ssl_delete(ctx_mutex_, ssl, ret); - return ret; + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); } + detail::shutdown_socket(sock); detail::close_socket(sock); - return false; + return ret; } // SSL HTTP client implementation @@ -5796,12 +7929,13 @@ inline SSLClient::SSLClient(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) : ClientImpl(host, port, client_cert_path, client_key_path) { - ctx_ = SSL_CTX_new(SSLv23_client_method()); + ctx_ = SSL_CTX_new(TLS_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); + if (!client_cert_path.empty() && !client_key_path.empty()) { if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), SSL_FILETYPE_PEM) != 1 || @@ -5816,12 +7950,13 @@ inline SSLClient::SSLClient(const std::string &host, int port, inline SSLClient::SSLClient(const std::string &host, int port, X509 *client_cert, EVP_PKEY *client_key) : ClientImpl(host, port) { - ctx_ = SSL_CTX_new(SSLv23_client_method()); + ctx_ = SSL_CTX_new(TLS_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); + if (client_cert != nullptr && client_key != nullptr) { if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { @@ -5833,18 +7968,25 @@ inline SSLClient::SSLClient(const std::string &host, int port, inline SSLClient::~SSLClient() { if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); } inline bool SSLClient::is_valid() const { return ctx_; } -inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path, - const char *ca_cert_dir_path) { - if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; } - if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; } -} - inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { - if (ca_cert_store) { ca_cert_store_ = ca_cert_store; } + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } } inline long SSLClient::get_openssl_verify_result() const { @@ -5853,24 +7995,28 @@ inline long SSLClient::get_openssl_verify_result() const { inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } -inline bool SSLClient::create_and_connect_socket(Socket &socket) { - return is_valid() && ClientImpl::create_and_connect_socket(socket); +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); } +// Assumes that socket_mutex_ is locked and that there are no requests in flight inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, - bool &success) { + bool &success, Error &error) { success = true; Response res2; - if (!detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { Request req2; req2.method = "CONNECT"; req2.path = host_and_port_; - return process_request(strm, req2, res2, false); + return process_request(strm, req2, res2, false, error); })) { - close_socket(socket, true); + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); success = false; return false; } @@ -5891,9 +8037,13 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, req3, auth, 1, detail::random_string(10), proxy_digest_auth_username_, proxy_digest_auth_password_, true)); - return process_request(strm, req3, res3, false); + return process_request(strm, req3, res3, false, error); })) { - close_socket(socket, true); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); success = false; return false; } @@ -5922,57 +8072,60 @@ inline bool SSLClient::load_certs() { ca_cert_dir_path_.c_str())) { ret = false; } - } else if (ca_cert_store_ != nullptr) { - if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) { - SSL_CTX_set_cert_store(ctx_, ca_cert_store_); - } } else { + auto loaded = false; #ifdef _WIN32 - detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); -#else - SSL_CTX_set_default_verify_paths(ctx_); -#endif + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } } }); return ret; } -inline bool SSLClient::initialize_ssl(Socket &socket) { +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { auto ssl = detail::ssl_new( socket.sock, ctx_, ctx_mutex_, - [&](SSL *ssl) { + [&](SSL *ssl2) { if (server_certificate_verification_) { if (!load_certs()) { - error_ = Error::SSLLoadingCerts; + error = Error::SSLLoadingCerts; return false; } - SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); } - if (SSL_connect(ssl) != 1) { - error_ = Error::SSLConnection; + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; return false; } if (server_certificate_verification_) { - verify_result_ = SSL_get_verify_result(ssl); + verify_result_ = SSL_get_verify_result(ssl2); if (verify_result_ != X509_V_OK) { - error_ = Error::SSLServerVerification; + error = Error::SSLServerVerification; return false; } - auto server_cert = SSL_get_peer_certificate(ssl); + auto server_cert = SSL_get1_peer_certificate(ssl2); if (server_cert == nullptr) { - error_ = Error::SSLServerVerification; + error = Error::SSLServerVerification; return false; } if (!verify_host(server_cert)) { X509_free(server_cert); - error_ = Error::SSLServerVerification; + error = Error::SSLServerVerification; return false; } X509_free(server_cert); @@ -5980,8 +8133,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket) { return true; }, - [&](SSL *ssl) { - SSL_set_tlsext_host_name(ssl, host_.c_str()); + [&](SSL *ssl2) { + SSL_set_tlsext_host_name(ssl2, host_.c_str()); return true; }); @@ -5990,26 +8143,35 @@ inline bool SSLClient::initialize_ssl(Socket &socket) { return true; } - close_socket(socket, false); + shutdown_socket(socket); + close_socket(socket); return false; } -inline void SSLClient::close_socket(Socket &socket, bool process_socket_ret) { - detail::close_socket(socket.sock); - socket_.sock = INVALID_SOCKET; - if (socket.ssl) { - detail::ssl_delete(ctx_mutex_, socket.ssl, process_socket_ret); - socket_.ssl = nullptr; +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); } inline bool -SSLClient::process_socket(Socket &socket, +SSLClient::process_socket(const Socket &socket, std::function callback) { assert(socket.ssl); return detail::process_client_socket_ssl( socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, callback); + write_timeout_sec_, write_timeout_usec_, std::move(callback)); } inline bool SSLClient::is_ssl() const { return true; } @@ -6065,7 +8227,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { if (alt_names) { auto dsn_matched = false; - auto ip_mached = false; + auto ip_matched = false; auto count = sk_GENERAL_NAME_num(alt_names); @@ -6075,22 +8237,20 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); - if (strlen(name) == name_len) { - switch (type) { - case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; - case GEN_IPADD: - if (!memcmp(&addr6, name, addr_len) || - !memcmp(&addr, name, addr_len)) { - ip_mached = true; - } - break; + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; } + break; } } } - if (dsn_matched || ip_mached) { ret = true; } + if (dsn_matched || ip_matched) { ret = true; } } GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); @@ -6143,15 +8303,16 @@ inline bool SSLClient::check_host_name(const char *pattern, #endif // Universal client implementation -inline Client::Client(const char *scheme_host_port) +inline Client::Client(const std::string &scheme_host_port) : Client(scheme_host_port, std::string(), std::string()) {} -inline Client::Client(const char *scheme_host_port, +inline Client::Client(const std::string &scheme_host_port, const std::string &client_cert_path, const std::string &client_key_path) { - const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)"); + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); - std::cmatch m; + std::smatch m; if (std::regex_match(scheme_host_port, m, re)) { auto scheme = m[1].str(); @@ -6159,6 +8320,10 @@ inline Client::Client(const char *scheme_host_port, if (!scheme.empty() && (scheme != "http" && scheme != "https")) { #else if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); #endif return; } @@ -6166,34 +8331,35 @@ inline Client::Client(const char *scheme_host_port, auto is_ssl = scheme == "https"; auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } - auto port_str = m[3].str(); + auto port_str = m[4].str(); auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); if (is_ssl) { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - cli_ = std::make_shared(host.c_str(), port, client_cert_path, - client_key_path); + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); is_ssl_ = is_ssl; #endif } else { - cli_ = std::make_shared(host.c_str(), port, client_cert_path, - client_key_path); + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); } } else { - cli_ = std::make_shared(scheme_host_port, 80, client_cert_path, - client_key_path); + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); } } inline Client::Client(const std::string &host, int port) - : cli_(std::make_shared(host, port)) {} + : cli_(detail::make_unique(host, port)) {} inline Client::Client(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) - : cli_(std::make_shared(host, port, client_cert_path, - client_key_path)) {} + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} inline Client::~Client() {} @@ -6201,198 +8367,351 @@ inline bool Client::is_valid() const { return cli_ != nullptr && cli_->is_valid(); } -inline Result Client::Get(const char *path) { return cli_->Get(path); } -inline Result Client::Get(const char *path, const Headers &headers) { +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { return cli_->Get(path, headers); } -inline Result Client::Get(const char *path, Progress progress) { - return cli_->Get(path, progress); +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, Progress progress) { - return cli_->Get(path, headers, progress); + return cli_->Get(path, headers, std::move(progress)); } -inline Result Client::Get(const char *path, ContentReceiver content_receiver) { +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { return cli_->Get(path, std::move(content_receiver)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver) { return cli_->Get(path, headers, std::move(content_receiver)); } -inline Result Client::Get(const char *path, ContentReceiver content_receiver, - Progress progress) { +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, headers, std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const char *path, ResponseHandler response_handler, +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, ContentReceiver content_receiver) { return cli_->Get(path, std::move(response_handler), std::move(content_receiver)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver) { return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver)); } -inline Result Client::Get(const char *path, ResponseHandler response_handler, +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, std::move(response_handler), std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const char *path, const Headers &headers, +inline Result Client::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { - return cli_->Get(path, headers, response_handler, content_receiver, progress); + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); } -inline Result Client::Head(const char *path) { return cli_->Head(path); } -inline Result Client::Head(const char *path, const Headers &headers) { +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { return cli_->Head(path, headers); } -inline Result Client::Post(const char *path) { return cli_->Post(path); } -inline Result Client::Post(const char *path, const std::string &body, - const char *content_type) { +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Post(path, body, content_type); } -inline Result Client::Post(const char *path, const Headers &headers, - const std::string &body, const char *content_type) { +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { return cli_->Post(path, headers, body, content_type); } -inline Result Client::Post(const char *path, size_t content_length, +inline Result Client::Post(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return cli_->Post(path, content_length, content_provider, content_type); -} -inline Result Client::Post(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return cli_->Post(path, headers, content_length, content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), content_type); } -inline Result Client::Post(const char *path, const Params ¶ms) { +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { return cli_->Post(path, params); } -inline Result Client::Post(const char *path, const Headers &headers, +inline Result Client::Post(const std::string &path, const Headers &headers, const Params ¶ms) { return cli_->Post(path, headers, params); } -inline Result Client::Post(const char *path, +inline Result Client::Post(const std::string &path, const MultipartFormDataItems &items) { return cli_->Post(path, items); } -inline Result Client::Post(const char *path, const Headers &headers, +inline Result Client::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items) { return cli_->Post(path, headers, items); } -inline Result Client::Put(const char *path) { return cli_->Put(path); } -inline Result Client::Put(const char *path, const std::string &body, - const char *content_type) { +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Put(path, body, content_type); } -inline Result Client::Put(const char *path, const Headers &headers, - const std::string &body, const char *content_type) { +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { return cli_->Put(path, headers, body, content_type); } -inline Result Client::Put(const char *path, size_t content_length, +inline Result Client::Put(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return cli_->Put(path, content_length, content_provider, content_type); -} -inline Result Client::Put(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return cli_->Put(path, headers, content_length, content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), content_type); } -inline Result Client::Put(const char *path, const Params ¶ms) { +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { return cli_->Put(path, params); } -inline Result Client::Put(const char *path, const Headers &headers, +inline Result Client::Put(const std::string &path, const Headers &headers, const Params ¶ms) { return cli_->Put(path, headers, params); } -inline Result Client::Patch(const char *path, const std::string &body, - const char *content_type) { +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Patch(path, body, content_type); } -inline Result Client::Patch(const char *path, const Headers &headers, - const std::string &body, const char *content_type) { +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { return cli_->Patch(path, headers, body, content_type); } -inline Result Client::Patch(const char *path, size_t content_length, +inline Result Client::Patch(const std::string &path, size_t content_length, ContentProvider content_provider, - const char *content_type) { - return cli_->Patch(path, content_length, content_provider, content_type); -} -inline Result Client::Patch(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return cli_->Patch(path, headers, content_length, content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), content_type); } -inline Result Client::Delete(const char *path) { return cli_->Delete(path); } -inline Result Client::Delete(const char *path, const std::string &body, - const char *content_type) { - return cli_->Delete(path, body, content_type); +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); } -inline Result Client::Delete(const char *path, const Headers &headers) { +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { return cli_->Delete(path, headers); } -inline Result Client::Delete(const char *path, const Headers &headers, +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, const std::string &body, - const char *content_type) { + const std::string &content_type) { return cli_->Delete(path, headers, body, content_type); } -inline Result Client::Options(const char *path) { return cli_->Options(path); } -inline Result Client::Options(const char *path, const Headers &headers) { +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { return cli_->Options(path, headers); } -inline bool Client::send(const Request &req, Response &res) { - return cli_->send(req, res); +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); } +inline Result Client::send(const Request &req) { return cli_->send(req); } + inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } +inline socket_t Client::socket() const { return cli_->socket(); } + inline void Client::stop() { cli_->stop(); } +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + inline void Client::set_default_headers(Headers headers) { cli_->set_default_headers(std::move(headers)); } +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + inline void Client::set_socket_options(SocketOptions socket_options) { - cli_->set_socket_options(socket_options); + cli_->set_socket_options(std::move(socket_options)); } inline void Client::set_connection_timeout(time_t sec, time_t usec) { cli_->set_connection_timeout(sec, usec); } + inline void Client::set_read_timeout(time_t sec, time_t usec) { cli_->set_read_timeout(sec, usec); } + inline void Client::set_write_timeout(time_t sec, time_t usec) { cli_->set_write_timeout(sec, usec); } -inline void Client::set_basic_auth(const char *username, const char *password) { +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { cli_->set_basic_auth(username, password); } -inline void Client::set_bearer_token_auth(const char *token) { +inline void Client::set_bearer_token_auth(const std::string &token) { cli_->set_bearer_token_auth(token); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_digest_auth(const char *username, - const char *password) { +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { cli_->set_digest_auth(username, password); } #endif @@ -6402,27 +8721,29 @@ inline void Client::set_follow_location(bool on) { cli_->set_follow_location(on); } +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + inline void Client::set_compress(bool on) { cli_->set_compress(on); } inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } -inline void Client::set_interface(const char *intf) { +inline void Client::set_interface(const std::string &intf) { cli_->set_interface(intf); } -inline void Client::set_proxy(const char *host, int port) { +inline void Client::set_proxy(const std::string &host, int port) { cli_->set_proxy(host, port); } -inline void Client::set_proxy_basic_auth(const char *username, - const char *password) { +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { cli_->set_proxy_basic_auth(username, password); } -inline void Client::set_proxy_bearer_token_auth(const char *token) { +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { cli_->set_proxy_bearer_token_auth(token); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_proxy_digest_auth(const char *username, - const char *password) { +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { cli_->set_proxy_digest_auth(username, password); } #endif @@ -6436,17 +8757,16 @@ inline void Client::enable_server_certificate_verification(bool enabled) { inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_ca_cert_path(const char *ca_cert_file_path, - const char *ca_cert_dir_path) { - if (is_ssl_) { - static_cast(*cli_).set_ca_cert_path(ca_cert_file_path, - ca_cert_dir_path); - } +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); } inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { if (is_ssl_) { static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); } } @@ -6467,4 +8787,8 @@ inline SSL_CTX *Client::ssl_context() const { } // namespace httplib +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + #endif // CPPHTTPLIB_HTTPLIB_H diff --git a/ext/hiredis-1.0.2/lib/ubuntu22.04/libhiredis.a b/ext/hiredis-1.0.2/lib/ubuntu22.04/amd64/libhiredis.a similarity index 100% rename from ext/hiredis-1.0.2/lib/ubuntu22.04/libhiredis.a rename to ext/hiredis-1.0.2/lib/ubuntu22.04/amd64/libhiredis.a diff --git a/ext/hiredis-1.0.2/lib/ubuntu22.04/arm64/libhiredis.a b/ext/hiredis-1.0.2/lib/ubuntu22.04/arm64/libhiredis.a new file mode 100644 index 000000000..081c92705 Binary files /dev/null and b/ext/hiredis-1.0.2/lib/ubuntu22.04/arm64/libhiredis.a differ diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/array b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/array similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/array rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/array diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/array.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/array.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/array.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/array.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/binarystring b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/binarystring similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/binarystring rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/binarystring diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/binarystring.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/binarystring.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/binarystring.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/binarystring.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/blob b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/blob similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/blob rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/blob diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/blob.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/blob.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/blob.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/blob.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/composite b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/composite similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/composite rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/composite diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/composite.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/composite.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/composite.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/composite.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/config-public-compiler.h b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/config-public-compiler.h similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/config-public-compiler.h rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/config-public-compiler.h diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/connection b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/connection similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/connection rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/connection diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/connection.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/connection.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/connection.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/cursor b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/cursor similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/cursor rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/cursor diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/dbtransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/dbtransaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/dbtransaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/dbtransaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/dbtransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/dbtransaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/dbtransaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/dbtransaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/errorhandler b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/errorhandler similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/errorhandler rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/errorhandler diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/errorhandler.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/errorhandler.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/errorhandler.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/errorhandler.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/except b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/except similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/except rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/except diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/except.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/except.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/except.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/except.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/field b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/field similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/field rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/field diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/field.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/field.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/field.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/field.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/array-composite.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/array-composite.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/array-composite.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/array-composite.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/callgate.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/callgate.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/callgate.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/callgate.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/concat.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/concat.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/concat.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/concat.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/conversions.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/conversions.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/conversions.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/conversions.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/encoding_group.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/encoding_group.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/encoding_group.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/encoding_group.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/encodings.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/encodings.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/encodings.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/encodings.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-errorhandler.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-errorhandler.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-errorhandler.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-errorhandler.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-largeobject.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-largeobject.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-largeobject.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-largeobject.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-notification_receiver.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-notification_receiver.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-notification_receiver.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-notification_receiver.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-pipeline.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-pipeline.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-pipeline.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-sql_cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-sql_cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-sql_cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-stream_from.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-stream_from.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-stream_from.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-stream_from.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-stream_to.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-stream_to.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-stream_to.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-stream_to.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-transaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-transaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/connection-transaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/connection-transaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/errorhandler-connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/errorhandler-connection.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/errorhandler-connection.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/errorhandler-connection.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-connection.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-connection.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-connection.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-creation.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-creation.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-creation.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-creation.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-pipeline.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-pipeline.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-pipeline.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-sql_cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/result-sql_cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/result-sql_cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/transaction-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/transaction-sql_cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/transaction-sql_cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/transaction-sql_cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/transaction-transaction_focus.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/transaction-transaction_focus.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/gates/transaction-transaction_focus.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/gates/transaction-transaction_focus.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/header-post.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/header-post.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/header-post.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/header-post.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/header-pre.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/header-pre.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/header-pre.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/header-pre.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/ignore-deprecated-post.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/ignore-deprecated-post.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/ignore-deprecated-post.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/ignore-deprecated-post.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/ignore-deprecated-pre.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/ignore-deprecated-pre.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/ignore-deprecated-pre.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/ignore-deprecated-pre.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/libpq-forward.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/libpq-forward.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/libpq-forward.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/libpq-forward.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/result_iter.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/result_iter.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/result_iter.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/result_iter.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/result_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/result_iterator.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/result_iterator.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/result_iterator.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/sql_cursor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/sql_cursor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/sql_cursor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/statement_parameters.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/statement_parameters.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/statement_parameters.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/statement_parameters.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/stream_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/stream_iterator.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/stream_iterator.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/stream_iterator.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/wait.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/wait.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/internal/wait.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/internal/wait.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/isolation b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/isolation similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/isolation rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/isolation diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/isolation.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/isolation.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/isolation.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/isolation.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/largeobject b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/largeobject similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/largeobject rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/largeobject diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/largeobject.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/largeobject.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/largeobject.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/largeobject.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/nontransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/nontransaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/nontransaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/nontransaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/nontransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/nontransaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/nontransaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/nontransaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/notification b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/notification similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/notification rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/notification diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/notification.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/notification.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/notification.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/notification.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/params b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/params similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/params rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/params diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/params.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/params.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/params.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/params.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pipeline b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pipeline similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pipeline rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pipeline diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pipeline.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pipeline.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pipeline.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pqxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pqxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/pqxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/pqxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/prepared_statement b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/prepared_statement similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/prepared_statement rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/prepared_statement diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/prepared_statement.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/prepared_statement.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/prepared_statement.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/prepared_statement.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/range b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/range similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/range rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/range diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/range.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/range.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/range.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/range.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/result b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/result similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/result rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/result diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/result.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/result.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/result.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/result.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/robusttransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/robusttransaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/robusttransaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/robusttransaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/robusttransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/robusttransaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/robusttransaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/robusttransaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/row b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/row rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/row.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/row.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/separated_list b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/separated_list similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/separated_list rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/separated_list diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/separated_list.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/separated_list.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/separated_list.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/separated_list.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/strconv b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/strconv similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/strconv rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/strconv diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/strconv.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/strconv.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/strconv.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/strconv.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_from b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_from similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_from rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_from diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_from.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_from.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_from.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_from.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_to b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_to similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_to rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_to diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_to.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_to.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/stream_to.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/stream_to.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/subtransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/subtransaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/subtransaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/subtransaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/subtransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/subtransaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/subtransaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/subtransaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/time b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/time similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/time rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/time diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/time.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/time.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/time.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/time.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_base b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_base similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_base rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_base diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_base.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_base.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_base.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_base.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_focus b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_focus similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_focus rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_focus diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_focus.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_focus.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transaction_focus.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transaction_focus.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transactor b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transactor similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transactor rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transactor diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transactor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transactor.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/transactor.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/transactor.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/types b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/types similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/types rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/types diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/types.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/types.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/types.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/types.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/util b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/util similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/util rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/util diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/util.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/util.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/util.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/util.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/version b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/version similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/version rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/version diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/version.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/version.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/version.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/version.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/zview b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/zview similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/zview rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/zview diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/zview.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/zview.hxx similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/include/pqxx/zview.hxx rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/zview.hxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-config-version.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-config-version.cmake similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-config-version.cmake rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-config-version.cmake diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-config.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-config.cmake similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-config.cmake rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-config.cmake diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-targets.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-targets.cmake similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/cmake/libpqxx/libpqxx-targets.cmake rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/cmake/libpqxx/libpqxx-targets.cmake diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/libpqxx-7.7.a b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/libpqxx-7.7.a similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/libpqxx-7.7.a rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/libpqxx-7.7.a diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/libpqxx.a b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/libpqxx.a similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/lib/libpqxx.a rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/lib/libpqxx.a diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/accessing-results.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/accessing-results.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/accessing-results.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/accessing-results.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/binary-data.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/binary-data.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/binary-data.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/binary-data.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/datatypes.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/datatypes.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/datatypes.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/datatypes.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/escaping.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/escaping.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/escaping.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/escaping.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/getting-started.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/getting-started.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/getting-started.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/getting-started.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/mainpage.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/mainpage.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/mainpage.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/mainpage.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/parameters.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/parameters.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/parameters.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/parameters.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/performance.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/performance.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/performance.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/performance.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/prepared-statement.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/prepared-statement.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/prepared-statement.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/prepared-statement.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/streams.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/streams.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/streams.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/streams.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/thread-safety.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/thread-safety.md similarity index 100% rename from ext/libpqxx-7.7.3/install/ubuntu22.04/share/doc/libpqxx/thread-safety.md rename to ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/share/doc/libpqxx/thread-safety.md diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array new file mode 100644 index 000000000..689f5b27b --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array @@ -0,0 +1,6 @@ +/** Handling of SQL arrays. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/array.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array.hxx new file mode 100644 index 000000000..8440a244f --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/array.hxx @@ -0,0 +1,103 @@ +/* Handling of SQL arrays. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/field instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ARRAY +#define PQXX_H_ARRAY + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#include "pqxx/internal/encoding_group.hxx" +#include "pqxx/internal/encodings.hxx" + + +namespace pqxx +{ +/// Low-level array parser. +/** Use this to read an array field retrieved from the database. + * + * This parser will only work reliably if your client encoding is UTF-8, ASCII, + * or a single-byte encoding which is a superset of ASCII (such as Latin-1). + * + * Also, the parser only supports array element types which use either a comma + * or a semicolon ("," or ";") as the separator between array elements. All + * built-in types use comma, except for one which uses semicolon, but some + * custom types may not work. + * + * The input is a C-style string containing the textual representation of an + * array, as returned by the database. The parser reads this representation + * on the fly. The string must remain in memory until parsing is done. + * + * Parse the array by making calls to @ref get_next until it returns a + * @ref juncture of "done". The @ref juncture tells you what the parser found + * in that step: did the array "nest" to a deeper level, or "un-nest" back up? + */ +class PQXX_LIBEXPORT array_parser +{ +public: + /// What's the latest thing found in the array? + enum class juncture + { + /// Starting a new row. + row_start, + /// Ending the current row. + row_end, + /// Found a NULL value. + null_value, + /// Found a string value. + string_value, + /// Parsing has completed. + done, + }; + + // TODO: constexpr noexcept. Breaks ABI. + /// Constructor. You don't need this; use @ref field::as_array instead. + /** The parser only remains valid while the data underlying the @ref result + * remains valid. Once all `result` objects referring to that data have been + * destroyed, the parser will no longer refer to valid memory. + */ + explicit array_parser( + std::string_view input, + internal::encoding_group = internal::encoding_group::MONOBYTE); + + /// Parse the next step in the array. + /** Returns what it found. If the juncture is @ref juncture::string_value, + * the string will contain the value. Otherwise, it will be empty. + * + * Call this until the @ref array_parser::juncture it returns is + * @ref juncture::done. + */ + std::pair get_next(); + +private: + std::string_view m_input; + internal::glyph_scanner_func *const m_scan; + + /// Current parsing position in the input. + std::string::size_type m_pos = 0u; + + std::string::size_type scan_single_quoted_string() const; + std::string parse_single_quoted_string(std::string::size_type end) const; + std::string::size_type scan_double_quoted_string() const; + std::string parse_double_quoted_string(std::string::size_type end) const; + std::string::size_type scan_unquoted_string() const; + std::string parse_unquoted_string(std::string::size_type end) const; + + std::string::size_type scan_glyph(std::string::size_type pos) const; + std::string::size_type + scan_glyph(std::string::size_type pos, std::string::size_type end) const; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring new file mode 100644 index 000000000..77551d9f7 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring @@ -0,0 +1,6 @@ +/** BYTEA (binary string) conversions. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/binarystring.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring.hxx new file mode 100644 index 000000000..47c82a035 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/binarystring.hxx @@ -0,0 +1,236 @@ +/* Deprecated representation for raw, binary data. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/binarystring instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_BINARYSTRING +#define PQXX_H_BINARYSTRING + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#include "pqxx/result.hxx" +#include "pqxx/strconv.hxx" + +namespace pqxx +{ +class binarystring; +template<> struct string_traits; + + +/// Binary data corresponding to PostgreSQL's "BYTEA" binary-string type. +/** @ingroup escaping-functions + * @deprecated Use @c std::basic_string and + * @c std::basic_string_view for binary data. In C++20 or better, + * any @c contiguous_range of @c std::byte will do. + * + * This class represents a binary string as stored in a field of type @c bytea. + * + * Internally a binarystring is zero-terminated, but it may also contain null + * bytes, they're just like any other byte value. So don't assume that it's + * safe to treat the contents as a C-style string. + * + * The binarystring retains its value even if the result it was obtained from + * is destroyed, but it cannot be copied or assigned. + * + * \relatesalso transaction_base::quote_raw + * + * To include a @c binarystring value in an SQL query, escape and quote it + * using the transaction's @c quote_raw function. + * + * @warning This class is implemented as a reference-counting smart pointer. + * Copying, swapping, and destroying binarystring objects that refer to the + * same underlying data block is not thread-safe. If you wish to pass + * binarystrings around between threads, make sure that each of these + * operations is protected against concurrency with similar operations on the + * same object, or other objects pointing to the same data block. + */ +class PQXX_LIBEXPORT binarystring +{ +public: + using char_type = unsigned char; + using value_type = std::char_traits::char_type; + using size_type = std::size_t; + using difference_type = long; + using const_reference = value_type const &; + using const_pointer = value_type const *; + using const_iterator = const_pointer; + using const_reverse_iterator = std::reverse_iterator; + + [[deprecated("Use std::byte for binary data.")]] binarystring( + binarystring const &) = default; + + /// Read and unescape bytea field. + /** The field will be zero-terminated, even if the original bytea field + * isn't. + * @param F the field to read; must be a bytea field + */ + [[deprecated("Use std::byte for binary data.")]] explicit binarystring( + field const &); + + /// Copy binary data from std::string_view on binary data. + /** This is inefficient in that it copies the data to a buffer allocated on + * the heap. + */ + [[deprecated("Use std::byte for binary data.")]] explicit binarystring( + std::string_view); + + /// Copy binary data of given length straight out of memory. + [[deprecated("Use std::byte for binary data.")]] binarystring( + void const *, std::size_t); + + /// Efficiently wrap a buffer of binary data in a @c binarystring. + [[deprecated("Use std::byte for binary data.")]] binarystring( + std::shared_ptr ptr, size_type size) : + m_buf{std::move(ptr)}, m_size{size} + {} + + /// Size of converted string in bytes. + [[nodiscard]] size_type size() const noexcept { return m_size; } + /// Size of converted string in bytes. + [[nodiscard]] size_type length() const noexcept { return size(); } + [[nodiscard]] bool empty() const noexcept { return size() == 0; } + + [[nodiscard]] const_iterator begin() const noexcept { return data(); } + [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); } + [[nodiscard]] const_iterator end() const noexcept { return data() + m_size; } + [[nodiscard]] const_iterator cend() const noexcept { return end(); } + + [[nodiscard]] const_reference front() const noexcept { return *begin(); } + [[nodiscard]] const_reference back() const noexcept + { + return *(data() + m_size - 1); + } + + [[nodiscard]] const_reverse_iterator rbegin() const + { + return const_reverse_iterator{end()}; + } + [[nodiscard]] const_reverse_iterator crbegin() const { return rbegin(); } + [[nodiscard]] const_reverse_iterator rend() const + { + return const_reverse_iterator{begin()}; + } + [[nodiscard]] const_reverse_iterator crend() const { return rend(); } + + /// Unescaped field contents. + [[nodiscard]] value_type const *data() const noexcept { return m_buf.get(); } + + [[nodiscard]] const_reference operator[](size_type i) const noexcept + { + return data()[i]; + } + + [[nodiscard]] PQXX_PURE bool operator==(binarystring const &) const noexcept; + [[nodiscard]] bool operator!=(binarystring const &rhs) const noexcept + { + return not operator==(rhs); + } + + binarystring &operator=(binarystring const &); + + /// Index contained string, checking for valid index. + const_reference at(size_type) const; + + /// Swap contents with other binarystring. + void swap(binarystring &); + + /// Raw character buffer (no terminating zero is added). + /** @warning No terminating zero is added! If the binary data did not end in + * a null character, you will not find one here. + */ + [[nodiscard]] char const *get() const noexcept + { + return reinterpret_cast(m_buf.get()); + } + + /// Read contents as a std::string_view. + [[nodiscard]] std::string_view view() const noexcept + { + return std::string_view(get(), size()); + } + + /// Read as regular C++ string (may include null characters). + /** This creates and returns a new string object. Don't call this + * repeatedly; retrieve your string once and keep it in a local variable. + * Also, do not expect to be able to compare the string's address to that of + * an earlier invocation. + */ + [[nodiscard]] std::string str() const; + + /// Access data as a pointer to @c std::byte. + [[nodiscard]] std::byte const *bytes() const + { + return reinterpret_cast(get()); + } + + /// Read data as a @c std::basic_string_view. + [[nodiscard]] std::basic_string_view bytes_view() const + { + return std::basic_string_view{bytes(), size()}; + } + +private: + std::shared_ptr m_buf; + size_type m_size{0}; +}; + + +template<> struct nullness : no_null +{}; + + +/// String conversion traits for @c binarystring. +/** Defines the conversions between a @c binarystring and its PostgreSQL + * textual format, for communication with the database. + * + * These conversions rely on the "hex" format which was introduced in + * PostgreSQL 9.0. Both your libpq and the server must be recent enough to + * speak this format. + */ +template<> struct string_traits +{ + static std::size_t size_buffer(binarystring const &value) noexcept + { + return internal::size_esc_bin(std::size(value)); + } + + static zview to_buf(char *begin, char *end, binarystring const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf(char *begin, char *end, binarystring const &value) + { + auto const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to escape binary data."}; + std::string_view text{value.view()}; + internal::esc_bin(binary_cast(text), begin); + return begin + budget; + } + + static binarystring from_string(std::string_view text) + { + auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; + std::shared_ptr buf{ + new unsigned char[size], [](unsigned char const *x) { delete[] x; }}; + pqxx::internal::unesc_bin(text, reinterpret_cast(buf.get())); +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return binarystring{std::move(buf), size}; +#include "pqxx/internal/ignore-deprecated-post.hxx" + } +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob new file mode 100644 index 000000000..3fd0afac9 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob @@ -0,0 +1,6 @@ +/** Binary Large Objects interface. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/blob.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob.hxx new file mode 100644 index 000000000..6d77be724 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/blob.hxx @@ -0,0 +1,351 @@ +/* Binary Large Objects interface. + * + * Read or write large objects, stored in their own storage on the server. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/largeobject instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_BLOB +#define PQXX_H_BLOB + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#if defined(PQXX_HAVE_PATH) +# include +#endif + +#if defined(PQXX_HAVE_RANGES) && __has_include() +# include +#endif + +#if defined(PQXX_HAVE_SPAN) && __has_include() +# include +#endif + +#include "pqxx/dbtransaction.hxx" + + +namespace pqxx +{ +/** Binary large object. + * + * This is how you store data that may be too large for the `BYTEA` type. + * Access operations are similar to those for a file: you can read, write, + * query or set the current reading/writing position, and so on. + * + * These large objects live in their own storage on the server, indexed by an + * integer object identifier ("oid"). + * + * Two `blob` objects may refer to the same actual large object in the + * database at the same time. Each will have its own reading/writing position, + * but writes to the one will of course affect what the other sees. + */ +class PQXX_LIBEXPORT blob +{ +public: + /// Create a new, empty large object. + /** You may optionally specify an oid for the new blob. If you do, then + * the new object will have that oid -- or creation will fail if there + * already is an object with that oid. + */ + [[nodiscard]] static oid create(dbtransaction &, oid = 0); + + /// Delete a large object, or fail if it does not exist. + static void remove(dbtransaction &, oid); + + /// Open blob for reading. Any attempt to write to it will fail. + [[nodiscard]] static blob open_r(dbtransaction &, oid); + // Open blob for writing. Any attempt to read from it will fail. + [[nodiscard]] static blob open_w(dbtransaction &, oid); + // Open blob for reading and/or writing. + [[nodiscard]] static blob open_rw(dbtransaction &, oid); + + /// You can default-construct a blob, but it won't do anything useful. + /** Most operations on a default-constructed blob will throw @ref + * usage_error. + */ + blob() = default; + + /// You can move a blob, but not copy it. The original becomes unusable. + blob(blob &&); + /// You can move a blob, but not copy it. The original becomes unusable. + blob &operator=(blob &&); + + blob(blob const &) = delete; + blob &operator=(blob const &) = delete; + ~blob(); + + /// Maximum number of bytes that can be read or written at a time. + /** The underlying protocol only supports reads and writes up to 2 GB + * exclusive. + * + * If you need to read or write more data to or from a binary large object, + * you'll have to break it up into chunks. + */ + static constexpr std::size_t chunk_limit = 0x7fffffff; + + /// Read up to `size` bytes of the object into `buf`. + /** Uses a buffer that you provide, resizing it as needed. If it suits you, + * this lets you allocate the buffer once and then re-use it multiple times. + * + * Resizes `buf` as needed. + * + * @warning The underlying protocol only supports reads up to 2GB at a time. + * If you need to read more, try making repeated calls to @ref append_to_buf. + */ + std::size_t read(std::basic_string &buf, std::size_t size); + +#if defined(PQXX_HAVE_SPAN) + /// Read up to `std::size(buf)` bytes from the object. + /** Retrieves bytes from the blob, at the current position, until `buf` is + * full or there are no more bytes to read, whichever comes first. + * + * Returns the filled portion of `buf`. This may be empty. + */ + template + std::span read(std::span buf) + { + return buf.subspan(0, raw_read(std::data(buf), std::size(buf))); + } +#endif // PQXX_HAVE_SPAN + +#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) + /// Read up to `std::size(buf)` bytes from the object. + /** Retrieves bytes from the blob, at the current position, until `buf` is + * full or there are no more bytes to read, whichever comes first. + * + * Returns the filled portion of `buf`. This may be empty. + */ + template std::span read(DATA &buf) + { + return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; + } +#else // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN + /// Read up to `std::size(buf)` bytes from the object. + /** @deprecated As libpqxx moves to C++20 as its baseline language version, + * this will take and return `std::span`. + * + * Retrieves bytes from the blob, at the current position, until `buf` is + * full (i.e. its current size is reached), or there are no more bytes to + * read, whichever comes first. + * + * This function will not change either the size or the capacity of `buf`, + * only its contents. + * + * Returns the filled portion of `buf`. This may be empty. + */ + template + std::basic_string_view read(std::vector &buf) + { + return {std::data(buf), raw_read(std::data(buf), std::size(buf))}; + } +#endif // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN + +#if defined(PQXX_HAVE_CONCEPTS) + /// Write `data` to large object, at the current position. + /** If the writing position is at the end of the object, this will append + * `data` to the object's contents and move the writing position so that + * it's still at the end. + * + * If the writing position was not at the end, writing will overwrite the + * prior data, but it will not remove data that follows the part where you + * wrote your new data. + * + * @warning This is a big difference from writing to a file. You can + * overwrite some data in a large object, but this does not truncate the + * data that was already there. For example, if the object contained binary + * data "abc", and you write "12" at the starting position, the object will + * contain "12c". + * + * @warning The underlying protocol only supports writes up to 2 GB at a + * time. If you need to write more, try making repeated calls to + * @ref append_from_buf. + */ + template void write(DATA const &data) + { + raw_write(std::data(data), std::size(data)); + } +#else + /// Write `data` large object, at the current position. + /** If the writing position is at the end of the object, this will append + * `data` to the object's contents and move the writing position so that + * it's still at the end. + * + * If the writing position was not at the end, writing will overwrite the + * prior data, but it will not remove data that follows the part where you + * wrote your new data. + * + * @warning This is a big difference from writing to a file. You can + * overwrite some data in a large object, but this does not truncate the + * data that was already there. For example, if the object contained binary + * data "abc", and you write "12" at the starting position, the object will + * contain "12c". + * + * @warning The underlying protocol only supports writes up to 2 GB at a + * time. If you need to write more, try making repeated calls to + * @ref append_from_buf. + */ + template void write(DATA const &data) + { + raw_write(std::data(data), std::size(data)); + } +#endif + + /// Resize large object to `size` bytes. + /** If the blob is more than `size` bytes long, this removes the end so as + * to make the blob the desired length. + * + * If the blob is less than `size` bytes long, it adds enough zero bytes to + * make it the desired length. + */ + void resize(std::int64_t size); + + /// Return the current reading/writing position in the large object. + [[nodiscard]] std::int64_t tell() const; + + /// Set the current reading/writing position to an absolute offset. + /** Returns the new file offset. */ + std::int64_t seek_abs(std::int64_t offset = 0); + /// Move the current reading/writing position forwards by an offset. + /** To move backwards, pass a negative offset. + * + * Returns the new file offset. + */ + std::int64_t seek_rel(std::int64_t offset = 0); + /// Set the current position to an offset relative to the end of the blob. + /** You'll probably want an offset of zero or less. + * + * Returns the new file offset. + */ + std::int64_t seek_end(std::int64_t offset = 0); + + /// Create a binary large object containing given `data`. + /** You may optionally specify an oid for the new object. If you do, and an + * object with that oid already exists, creation will fail. + */ + static oid from_buf( + dbtransaction &tx, std::basic_string_view data, oid id = 0); + + /// Append `data` to binary large object. + /** The underlying protocol only supports appending blocks up to 2 GB. + */ + static void append_from_buf( + dbtransaction &tx, std::basic_string_view data, oid id); + + /// Read client-side file and store it server-side as a binary large object. + [[nodiscard]] static oid from_file(dbtransaction &, char const path[]); + +#if defined(PQXX_HAVE_PATH) && !defined(_WIN32) + /// Read client-side file and store it server-side as a binary large object. + /** This overload is not available on Windows, where `std::filesystem::path` + * converts to a `wchar_t` string rather than a `char` string. + */ + [[nodiscard]] static oid + from_file(dbtransaction &tx, std::filesystem::path const &path) + { + return from_file(tx, path.c_str()); + } +#endif + + /// Read client-side file and store it server-side as a binary large object. + /** In this version, you specify the binary large object's oid. If that oid + * is already in use, the operation will fail. + */ + static oid from_file(dbtransaction &, char const path[], oid); + +#if defined(PQXX_HAVE_PATH) && !defined(_WIN32) + /// Read client-side file and store it server-side as a binary large object. + /** In this version, you specify the binary large object's oid. If that oid + * is already in use, the operation will fail. + * + * This overload is not available on Windows, where `std::filesystem::path` + * converts to a `wchar_t` string rather than a `char` string. + */ + static oid + from_file(dbtransaction &tx, std::filesystem::path const &path, oid id) + { + return from_file(tx, path.c_str(), id); + } +#endif + + /// Convenience function: Read up to `max_size` bytes from blob with `id`. + /** You could easily do this yourself using the @ref open_r and @ref read + * functions, but it can save you a bit of code to do it this way. + */ + static void to_buf( + dbtransaction &, oid, std::basic_string &, + std::size_t max_size); + + /// Read part of the binary large object with `id`, and append it to `buf`. + /** Use this to break up a large read from one binary large object into one + * massive buffer. Just keep calling this function until it returns zero. + * + * The `offset` is how far into the large object your desired chunk is, and + * `append_max` says how much to try and read in one go. + */ + static std::size_t append_to_buf( + dbtransaction &tx, oid id, std::int64_t offset, + std::basic_string &buf, std::size_t append_max); + + /// Write a binary large object's contents to a client-side file. + static void to_file(dbtransaction &, oid, char const path[]); + +#if defined(PQXX_HAVE_PATH) && !defined(_WIN32) + /// Write a binary large object's contents to a client-side file. + /** This overload is not available on Windows, where `std::filesystem::path` + * converts to a `wchar_t` string rather than a `char` string. + */ + static void + to_file(dbtransaction &tx, oid id, std::filesystem::path const &path) + { + to_file(tx, id, path.c_str()); + } +#endif + + /// Close this blob. + /** This does not delete the blob from the database; it only terminates your + * local object for accessing the blob. + * + * Resets the blob to a useless state similar to one that was + * default-constructed. + * + * The destructor will do this for you automatically. Still, there is a + * reason to `close()` objects explicitly where possible: if an error should + * occur while closing, `close()` can throw an exception. A destructor + * cannot. + */ + void close(); + +private: + PQXX_PRIVATE blob(connection &conn, int fd) noexcept : + m_conn{&conn}, m_fd{fd} + {} + static PQXX_PRIVATE blob open_internal(dbtransaction &, oid, int); + static PQXX_PRIVATE pqxx::internal::pq::PGconn * + raw_conn(pqxx::connection *) noexcept; + static PQXX_PRIVATE pqxx::internal::pq::PGconn * + raw_conn(pqxx::dbtransaction const &) noexcept; + static PQXX_PRIVATE std::string errmsg(connection const *); + static PQXX_PRIVATE std::string errmsg(dbtransaction const &tx) + { + return errmsg(&tx.conn()); + } + PQXX_PRIVATE std::string errmsg() const { return errmsg(m_conn); } + PQXX_PRIVATE std::int64_t seek(std::int64_t offset, int whence); + std::size_t raw_read(std::byte buf[], std::size_t size); + void raw_write(std::byte const buf[], std::size_t size); + + connection *m_conn = nullptr; + int m_fd = -1; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite new file mode 100644 index 000000000..2bfa7ade9 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite @@ -0,0 +1,6 @@ +/** Handling of SQL "composite types." + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/composite.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite.hxx new file mode 100644 index 000000000..439b133a8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/composite.hxx @@ -0,0 +1,149 @@ +#ifndef PQXX_H_COMPOSITE +#define PQXX_H_COMPOSITE + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/internal/array-composite.hxx" +#include "pqxx/internal/concat.hxx" +#include "pqxx/util.hxx" + +namespace pqxx +{ +/// Parse a string representation of a value of a composite type. +/** @warning This code is still experimental. Use with care. + * + * You may use this as a helper while implementing your own @ref string_traits + * for a composite type. + * + * This function interprets `text` as the string representation of a value of + * some composite type, and sets each of `fields` to the respective values of + * its fields. The field types must be copy-assignable. + * + * The number of fields must match the number of fields in the composite type, + * and there must not be any other text in the input. The function is meant to + * handle any value string that the backend can produce, but not necessarily + * every valid alternative spelling. + * + * Fields in composite types can be null. When this happens, the C++ type of + * the corresponding field reference must be of a type that can handle nulls. + * If you are working with a type that does not have an inherent null value, + * such as e.g. `int`, consider using `std::optional`. + */ +template +inline void parse_composite( + pqxx::internal::encoding_group enc, std::string_view text, T &...fields) +{ + static_assert(sizeof...(fields) > 0); + + auto const scan{pqxx::internal::get_glyph_scanner(enc)}; + auto const data{std::data(text)}; + auto const size{std::size(text)}; + if (size == 0) + throw conversion_error{"Cannot parse composite value from empty string."}; + + std::size_t here{0}, next{scan(data, size, here)}; + if (next != 1 or data[here] != '(') + throw conversion_error{ + internal::concat("Invalid composite value string: ", text)}; + + here = next; + + constexpr auto num_fields{sizeof...(fields)}; + std::size_t index{0}; + (pqxx::internal::parse_composite_field( + index, text, here, fields, scan, num_fields - 1), + ...); + if (here != std::size(text)) + throw conversion_error{internal::concat( + "Composite value did not end at the closing parenthesis: '", text, + "'.")}; + if (text[here - 1] != ')') + throw conversion_error{internal::concat( + "Composive value did not end in parenthesis: '", text, "'")}; +} + + +/// Parse a string representation of a value of a composite type. +/** @warning This version only works for UTF-8 and single-byte encodings. + * + * For proper encoding support, use the composite-type support in the + * `field` class. + */ +template +inline void parse_composite(std::string_view text, T &...fields) +{ + parse_composite(pqxx::internal::encoding_group::MONOBYTE, text, fields...); +} +} // namespace pqxx + + +namespace pqxx::internal +{ +constexpr char empty_composite_str[]{"()"}; +} // namespace pqxx::internal + + +namespace pqxx +{ +/// Estimate the buffer size needed to represent a value of a composite type. +/** Returns a conservative estimate. + */ +template +[[nodiscard]] inline std::size_t +composite_size_buffer(T const &...fields) noexcept +{ + constexpr auto num{sizeof...(fields)}; + + // Size for a multi-field composite includes room for... + // + opening parenthesis + // + field budgets + // + separating comma per field + // - comma after final field + // + closing parenthesis + // + terminating zero + + if constexpr (num == 0) + return std::size(pqxx::internal::empty_composite_str); + else + return 1 + (pqxx::internal::size_composite_field_buffer(fields) + ...) + + num + 1; +} + + +/// Render a series of values as a single composite SQL value. +/** @warning This code is still experimental. Use with care. + * + * You may use this as a helper while implementing your own `string_traits` + * for a composite type. + */ +template +inline char *composite_into_buf(char *begin, char *end, T const &...fields) +{ + if (std::size_t(end - begin) < composite_size_buffer(fields...)) + throw conversion_error{ + "Buffer space may not be enough to represent composite value."}; + + constexpr auto num_fields{sizeof...(fields)}; + if constexpr (num_fields == 0) + { + constexpr char empty[]{"()"}; + std::memcpy(begin, empty, std::size(empty)); + return begin + std::size(empty); + } + + char *pos{begin}; + *pos++ = '('; + + (pqxx::internal::write_composite_field(pos, end, fields), ...); + + // If we've got multiple fields, "backspace" that last comma. + if constexpr (num_fields > 1) + --pos; + *pos++ = ')'; + *pos++ = '\0'; + return pos; +} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/config-public-compiler.h b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/config-public-compiler.h new file mode 100644 index 000000000..3668a10f8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/config-public-compiler.h @@ -0,0 +1,81 @@ +/* include/pqxx/config.h.in. Generated from configure.ac by autoheader. */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DLFCN_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_INTTYPES_H */ +/* Define to 1 if you have the `pq' library (-lpq). */ +/* #undef HAVE_LIBPQ */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MEMORY_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STDINT_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STDLIB_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STRINGS_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STRING_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_STAT_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_TYPES_H */ +/* Define to 1 if you have the header file. */ +/* #undef HAVE_UNISTD_H */ +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +/* #undef LT_OBJDIR */ +/* Name of package */ +/* #undef PACKAGE */ +/* Define to the address where bug reports for this package should be sent. */ +/* #undef PACKAGE_BUGREPORT */ +/* Define to the full name of this package. */ +/* #undef PACKAGE_NAME */ +/* Define to the full name and version of this package. */ +/* #undef PACKAGE_STRING */ +/* Define to the one symbol short name of this package. */ +/* #undef PACKAGE_TARNAME */ +/* Define to the home page for this package. */ +/* #undef PACKAGE_URL */ +/* Define to the version of this package. */ +/* #undef PACKAGE_VERSION */ +/* Define if supports floating-point conversion. */ +#define PQXX_HAVE_CHARCONV_FLOAT +/* Define if supports integer conversion. */ +#define PQXX_HAVE_CHARCONV_INT +/* Define if compiler has C++20 std::cmp_greater etc. */ +/* #undef PQXX_HAVE_CMP */ +/* Define if compiler supports Concepts and header. */ +/* #undef PQXX_HAVE_CONCEPTS */ +/* Define if compiler supports __cxa_demangle */ +#define PQXX_HAVE_CXA_DEMANGLE +/* Define if g++ supports pure attribute */ +#define PQXX_HAVE_GCC_PURE +/* Define if g++ supports visibility attribute. */ +#define PQXX_HAVE_GCC_VISIBILITY +/* Define if likely & unlikely work. */ +/* #undef PQXX_HAVE_LIKELY */ +/* Define if operator[] can take multiple arguments. */ +/* #undef PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT */ +/* Define if compiler has usable std::filesystem::path. */ +#define PQXX_HAVE_PATH +/* Define if poll() is available. */ +#define PQXX_HAVE_POLL +/* Define if libpq has PQencryptPasswordConn (since pg 10). */ +#define PQXX_HAVE_PQENCRYPTPASSWORDCONN +/* Define if libpq has pipeline mode (since pg 14). */ +#define PQXX_HAVE_PQ_PIPELINE +/* Define if std::this_thread::sleep_for works. */ +#define PQXX_HAVE_SLEEP_FOR +/* Define if compiler has std::span. */ +/* #undef PQXX_HAVE_SPAN */ +/* Define if strerror_r() is available. */ +#define PQXX_HAVE_STRERROR_R +/* Define if strerror_s() is available. */ +/* #undef PQXX_HAVE_STRERROR_S */ +/* Define if thread_local is fully supported. */ +#define PQXX_HAVE_THREAD_LOCAL +/* Define if std::chrono has year_month_day etc. */ +/* #undef PQXX_HAVE_YEAR_MONTH_DAY */ +/* Define to 1 if you have the ANSI C header files. */ +/* #undef STDC_HEADERS */ +/* Version number of package */ +/* #undef VERSION */ diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection new file mode 100644 index 000000000..82ff43aa5 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection @@ -0,0 +1,8 @@ +/** pqxx::connection class. + * + * pqxx::connection encapsulates a connection to a database. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/connection.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection.hxx new file mode 100644 index 000000000..92454bb47 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/connection.hxx @@ -0,0 +1,1261 @@ +/* Definition of the connection class. + * + * pqxx::connection encapsulates a connection to a database. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/connection instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_CONNECTION +#define PQXX_H_CONNECTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Double-check in order to suppress an overzealous Visual C++ warning (#418). +#if defined(PQXX_HAVE_CONCEPTS) && __has_include() +# include +#endif + +#include "pqxx/errorhandler.hxx" +#include "pqxx/except.hxx" +#include "pqxx/internal/concat.hxx" +#include "pqxx/params.hxx" +#include "pqxx/separated_list.hxx" +#include "pqxx/strconv.hxx" +#include "pqxx/types.hxx" +#include "pqxx/util.hxx" +#include "pqxx/zview.hxx" + + +/** + * @addtogroup connections + * + * Use of the libpqxx library starts here. + * + * Everything that can be done with a database through libpqxx must go through + * a @ref pqxx::connection object. It connects to a database when you create + * it, and it terminates that communication during destruction. + * + * Many things come together in this class. Handling of error and warning + * messages, for example, is defined by @ref pqxx::errorhandler objects in the + * context of a connection. Prepared statements are also defined here. + * + * When you connect to a database, you pass a connection string containing any + * parameters and options, such as the server address and the database name. + * + * These are identical to the ones in libpq, the C language binding upon which + * libpqxx itself is built: + * + * https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING + * + * There are also environment variables you can set to provide defaults, again + * as defined by libpq: + * + * https://www.postgresql.org/docs/current/libpq-envars.html + * + * You can also create a database connection _asynchronously_ using an + * intermediate @ref pqxx::connecting object. + */ + +namespace pqxx::internal +{ +class sql_cursor; + +#if defined(PQXX_HAVE_CONCEPTS) +/// Concept: T is a range of pairs of zero-terminated strings. +template +concept ZKey_ZValues = std::ranges::input_range and requires(T t) +{ + {std::cbegin(t)}; + { + std::get<0>(*std::cbegin(t)) + } -> ZString; + { + std::get<1>(*std::cbegin(t)) + } -> ZString; +} and std::tuple_size_v::value_type> +== 2; +#endif // PQXX_HAVE_CONCEPTS +} // namespace pqxx::internal + + +namespace pqxx::internal::gate +{ +class connection_dbtransaction; +class connection_errorhandler; +class connection_largeobject; +class connection_notification_receiver; +class connection_pipeline; +class connection_sql_cursor; +class connection_stream_from; +class connection_stream_to; +class connection_transaction; +class const_connection_largeobject; +} // namespace pqxx::internal::gate + + +namespace pqxx +{ +/// Representation of a PostgreSQL table path. +/** A "table path" consists of a table name, optionally prefixed by a schema + * name, which in turn is optionally prefixed by a database name. + * + * A minimal example of a table path would be `{mytable}`. But a table path + * may also take the forms `{myschema,mytable}` or + * `{mydb,myschema,mytable}`. + */ +using table_path = std::initializer_list; + + +/// Encrypt a password. @deprecated Use connection::encrypt_password instead. +[[nodiscard, + deprecated("Use connection::encrypt_password instead.")]] std::string + PQXX_LIBEXPORT + encrypt_password(char const user[], char const password[]); + +/// Encrypt password. @deprecated Use connection::encrypt_password instead. +[[nodiscard, + deprecated("Use connection::encrypt_password instead.")]] inline std::string +encrypt_password(zview user, zview password) +{ +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return encrypt_password(user.c_str(), password.c_str()); +#include "pqxx/internal/ignore-deprecated-post.hxx" +} + + +/// Error verbosity levels. +enum class error_verbosity : int +{ + // These values must match those in libpq's PGVerbosity enum. + terse = 0, + normal = 1, + verbose = 2 +}; + + +/// Connection to a database. +/** This is the first class to look at when you wish to work with a database + * through libpqxx. The connection opens during construction, and closes upon + * destruction. + * + * When creating a connection, you can pass a connection URI or a postgres + * connection string, to specify the database server's address, a login + * username, and so on. If you don't, the connection will try to obtain them + * from certain environment variables. If those are not set either, the + * default is to try and connect to the local system's port 5432. + * + * Find more about connection strings here: + * + * https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING + * + * The variables are documented here: + * + * https://www.postgresql.org/docs/current/libpq-envars.html + * + * To query or manipulate the database once connected, use one of the + * transaction classes (see pqxx/transaction_base.hxx) and perhaps also the + * transactor framework (see pqxx/transactor.hxx). + * + * When a connection breaks, you will typically get a @ref broken_connection + * exception. This can happen at almost any point. + * + * @warning On Unix-like systems, including GNU and BSD systems, your program + * may receive the SIGPIPE signal when the connection to the backend breaks. By + * default this signal will abort your program. Use "signal(SIGPIPE, SIG_IGN)" + * if you want your program to continue running after a connection fails. + */ +class PQXX_LIBEXPORT connection +{ +public: + connection() : connection{""} {} + + /// Connect to a database, using `options` string. + explicit connection(char const options[]) + { + check_version(); + init(options); + } + + /// Connect to a database, using `options` string. + explicit connection(zview options) : connection{options.c_str()} + { + // (Delegates to other constructor which calls check_version for us.) + } + + /// Move constructor. + /** Moving a connection is not allowed if it has an open transaction, or has + * error handlers or notification receivers registered on it. In those + * situations, other objects may hold references to the old object which + * would become invalid and might produce hard-to-diagnose bugs. + */ + connection(connection &&rhs); + +#if defined(PQXX_HAVE_CONCEPTS) + /// Connect to a database, passing options as a range of key/value pairs. + /** @warning Experimental. Requires C++20 "concepts" support. Define + * `PQXX_HAVE_CONCEPTS` to enable it. + * + * There's no need to escape the parameter values. + * + * See the PostgreSQL libpq documentation for the full list of possible + * options: + * + * https://postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS + * + * The options can be anything that can be iterated as a series of pairs of + * zero-terminated strings: `std::pair`, or + * `std::tuple`, or + * `std::map`, and so on. + */ + template + inline connection(MAPPING const ¶ms); +#endif // PQXX_HAVE_CONCEPTS + + ~connection() + { + try + { + close(); + } + catch (std::exception const &) + {} + } + + /// Move assignment. + /** Neither connection can have an open transaction, registered error + * handlers, or registered notification receivers. + */ + connection &operator=(connection &&rhs); + + connection(connection const &) = delete; + connection &operator=(connection const &) = delete; + + /// Is this connection open at the moment? + /** @warning This function is **not** needed in most code. Resist the + * temptation to check it after opening a connection. The `connection` + * constructor will throw a @ref broken_connection exception if can't connect + * to the database. + */ + [[nodiscard]] bool PQXX_PURE is_open() const noexcept; + + /// Invoke notice processor function. The message should end in newline. + void process_notice(char const[]) noexcept; + /// Invoke notice processor function. Newline at end is recommended. + /** The zview variant, with a message ending in newline, is the most + * efficient way to call process_notice. + */ + void process_notice(zview) noexcept; + + /// Enable tracing to a given output stream, or nullptr to disable. + void trace(std::FILE *) noexcept; + + /** + * @name Connection properties + * + * These are probably not of great interest, since most are derived from + * information supplied by the client program itself, but they are included + * for completeness. + * + * The connection needs to be currently active for these to work. + */ + //@{ + /// Name of database we're connected to, if any. + [[nodiscard]] char const *dbname() const; + + /// Database user ID we're connected under, if any. + [[nodiscard]] char const *username() const; + + /// Address of server, or nullptr if none specified (i.e. default or local) + [[nodiscard]] char const *hostname() const; + + /// Server port number we're connected to. + [[nodiscard]] char const *port() const; + + /// Process ID for backend process, or 0 if inactive. + [[nodiscard]] int PQXX_PURE backendpid() const &noexcept; + + /// Socket currently used for connection, or -1 for none. Use with care! + /** Query the current socket number. This is intended for event loops based + * on functions such as select() or poll(), where you're waiting for any of + * multiple file descriptors to become ready for communication. + * + * Please try to stay away from this function. It is really only meant for + * event loops that need to wait on more than one file descriptor. If all + * you need is to block until a notification arrives, for instance, use + * await_notification(). If you want to issue queries and retrieve results + * in nonblocking fashion, check out the pipeline class. + */ + [[nodiscard]] int PQXX_PURE sock() const &noexcept; + + /// What version of the PostgreSQL protocol is this connection using? + /** The answer can be 0 (when there is no connection); 3 for protocol 3.0; or + * possibly higher values as newer protocol versions come into use. + */ + [[nodiscard]] int PQXX_PURE protocol_version() const noexcept; + + /// What version of the PostgreSQL server are we connected to? + /** The result is a bit complicated: each of the major, medium, and minor + * release numbers is written as a two-digit decimal number, and the three + * are then concatenated. Thus server version 9.4.2 will be returned as the + * decimal number 90402. If there is no connection to the server, this + * returns zero. + * + * @warning When writing version numbers in your code, don't add zero at the + * beginning! Numbers beginning with zero are interpreted as octal (base-8) + * in C++. Thus, 070402 is not the same as 70402, and 080000 is not a number + * at all because there is no digit "8" in octal notation. Use strictly + * decimal notation when it comes to these version numbers. + */ + [[nodiscard]] int PQXX_PURE server_version() const noexcept; + //@} + + /// @name Text encoding + /** + * Each connection is governed by a "client encoding," which dictates how + * strings and other text is represented in bytes. The database server will + * send text data to you in this encoding, and you should use it for the + * queries and data which you send to the server. + * + * Search the PostgreSQL documentation for "character set encodings" to find + * out more about the available encodings, how to extend them, and how to use + * them. Not all server-side encodings are compatible with all client-side + * encodings or vice versa. + * + * Encoding names are case-insensitive, so e.g. "UTF8" is equivalent to + * "utf8". + * + * You can change the client encoding, but this may not work when the + * connection is in a special state, such as when streaming a table. It's + * not clear what happens if you change the encoding during a transaction, + * and then abort the transaction. + */ + //@{ + /// Get client-side character encoding, by name. + [[nodiscard]] std::string get_client_encoding() const; + + /// Set client-side character encoding, by name. + /** + * @param encoding Name of the character set encoding to use. + */ + void set_client_encoding(zview encoding) & + { + set_client_encoding(encoding.c_str()); + } + + /// Set client-side character encoding, by name. + /** + * @param encoding Name of the character set encoding to use. + */ + void set_client_encoding(char const encoding[]) &; + + /// Get the connection's encoding, as a PostgreSQL-defined code. + [[nodiscard]] int PQXX_PRIVATE encoding_id() const; + + //@} + + /// Set session variable, using SQL's `SET` command. + /** @deprecated To set a session variable, use @ref set_session_var. To set + * a transaction-local variable, execute an SQL `SET` command. + * + * @warning When setting a string value, you must escape and quote it first. + * Use the @ref quote() function to do that. + * + * @warning This executes an SQL query, so do not get or set variables while + * a table stream or pipeline is active on the same connection. + * + * @param var Variable to set. + * @param value New value for Var. This can be any SQL expression. If it's + * a string, be sure that it's properly escaped and quoted. + */ + [[deprecated("To set session variables, use set_session_var.")]] void + set_variable(std::string_view var, std::string_view value) &; + + /// Set one of the session variables to a new value. + /** This executes SQL, so do not do it while a pipeline or stream is active + * on the connection. + * + * The value you set here will last for the rest of the connection's + * duration, or until you set a new value. + * + * If you set the value while in a @ref dbtransaction (i.e. any transaction + * that is not a @ref nontransaction), then rolling back the transaction will + * undo the change. + * + * All applies to setting _session_ variables. You can also set the same + * variables as _local_ variables, in which case they will always revert to + * their previous value when the transaction ends (or when you overwrite them + * of course). To set a local variable, simply execute an SQL statement + * along the lines of "`SET LOCAL var = 'value'`" inside your transaction. + * + * @param var The variable to set. + * @param value The new value for the variable. + * @throw @ref variable_set_to_null if the value is null; this is not + * allowed. + */ + template + void set_session_var(std::string_view var, TYPE const &value) & + { + if constexpr (nullness::has_null) + { + if (nullness::is_null(value)) + throw variable_set_to_null{ + internal::concat("Attempted to set variable ", var, " to null.")}; + } + exec(internal::concat("SET ", quote_name(var), "=", quote(value))); + } + + /// Read session variable, using SQL's `SHOW` command. + /** @warning This executes an SQL query, so do not get or set variables while + * a table stream or pipeline is active on the same connection. + */ + [[deprecated("Use get_var instead.")]] std::string + get_variable(std::string_view); + + /// Read currently applicable value of a variable. + /** This function executes an SQL statement, so it won't work while a + * @ref pipeline or query stream is active on the connection. + * + * @return a blank `std::optional` if the variable's value is null, or its + * string value otherwise. + */ + std::string get_var(std::string_view var); + + /// Read currently applicable value of a variable. + /** This function executes an SQL statement, so it won't work while a + * @ref pipeline or query stream is active on the connection. + * + * If there is any possibility that the variable is null, ensure that `TYPE` + * can represent null values. + */ + template TYPE get_var_as(std::string_view var) + { + return from_string(get_var(var)); + } + + /** + * @name Notifications and Receivers + */ + //@{ + /// Check for pending notifications and take appropriate action. + /** This does not block. To wait for incoming notifications, either call + * await_notification() (it calls this function); or wait for incoming data + * on the connection's socket (i.e. wait to read), and then call this + * function repeatedly until it returns zero. After that, there are no more + * pending notifications so you may want to wait again. + * + * If any notifications are pending when you call this function, it + * processes them by finding any receivers that match the notification string + * and invoking those. If no receivers match, there is nothing to invoke but + * we do consider the notification processed. + * + * If any of the client-registered receivers throws an exception, the + * function will report it using the connection's errorhandlers. It does not + * re-throw the exceptions. + * + * @return Number of notifications processed. + */ + int get_notifs(); + + /// Wait for a notification to come in. + /** There are other events that will also terminate the wait, such as the + * backend failing. It will also wake up periodically. + * + * If a notification comes in, the call will process it, along with any other + * notifications that may have been pending. + * + * To wait for notifications into your own event loop instead, wait until + * there is incoming data on the connection's socket to be read, then call + * @ref get_notifs() repeatedly until it returns zero. + * + * @return Number of notifications processed. + */ + int await_notification(); + + /// Wait for a notification to come in, or for given timeout to pass. + /** There are other events that will also terminate the wait, such as the + * backend failing, or timeout expiring. + * + * If a notification comes in, the call will process it, along with any other + * notifications that may have been pending. + * + * To wait for notifications into your own event loop instead, wait until + * there is incoming data on the connection's socket to be read, then call + * @ref get_notifs repeatedly until it returns zero. + * + * @return Number of notifications processed + */ + int await_notification(std::time_t seconds, long microseconds); + //@} + + /** + * @name Password encryption + * + * Use this when setting a new password for the user if password encryption + * is enabled. Inputs are the SQL name for the user for whom you with to + * encrypt a password; the plaintext password; and the hash algorithm. + * + * The algorithm must be one of "md5", "scram-sha-256" (introduced in + * PostgreSQL 10), or `nullptr`. If the pointer is null, this will query + * the `password_encryption setting` from the server, and use the default + * algorithm as defined there. + * + * @return encrypted version of the password, suitable for encrypted + * PostgreSQL authentication. + * + * Thus you can change a user's password with: + * ```cxx + * void setpw(transaction_base &t, string const &user, string const &pw) + * { + * t.exec0("ALTER USER " + user + " " + * "PASSWORD '" + t.conn().encrypt_password(user,pw) + "'"); + * } + * ``` + * + * When building this against a libpq older than version 10, this will use + * an older function which only supports md5. In that case, requesting a + * different algorithm than md5 will result in a @ref feature_not_supported + * exception. + */ + //@{ + /// Encrypt a password for a given user. + [[nodiscard]] std::string + encrypt_password(zview user, zview password, zview algorithm) + { + return encrypt_password(user.c_str(), password.c_str(), algorithm.c_str()); + } + /// Encrypt a password for a given user. + [[nodiscard]] std::string encrypt_password( + char const user[], char const password[], char const *algorithm = nullptr); + //@} + + /** + * @name Prepared statements + * + * PostgreSQL supports prepared SQL statements, i.e. statements that you can + * register under a name you choose, optimized once by the backend, and + * executed any number of times under the given name. + * + * Prepared statement definitions are not sensitive to transaction + * boundaries. A statement defined inside a transaction will remain defined + * outside that transaction, even if the transaction itself is subsequently + * aborted. Once a statement has been prepared, it will only go away if you + * close the connection or explicitly "unprepare" the statement. + * + * Use the `pqxx::transaction_base::exec_prepared` functions to execute a + * prepared statement. See @ref prepared for a full discussion. + * + * @warning Using prepared statements can save time, but if your statement + * takes parameters, it may also make your application significantly slower! + * The reason is that the server works out a plan for executing the query + * when you prepare it. At that time, of course it does not know the values + * for the parameters that you will pass. If you execute a query without + * preparing it, then the server works out the plan on the spot, with full + * knowledge of the parameter values. + * + * A statement's definition can refer to its parameters as `$1`, `$2`, etc. + * The first parameter you pass to the call provides a value for `$1`, and + * so on. + * + * Here's an example of how to use prepared statements. + * + * ```cxx + * using namespace pqxx; + * void foo(connection &c) + * { + * c.prepare("findtable", "select * from pg_tables where name=$1"); + * work tx{c}; + * result r = tx.exec_prepared("findtable", "mytable"); + * if (std::empty(r)) throw runtime_error{"mytable not found!"}; + * } + * ``` + */ + //@{ + + /// Define a prepared statement. + /** + * @param name unique name for the new prepared statement. + * @param definition SQL statement to prepare. + */ + void prepare(zview name, zview definition) & + { + prepare(name.c_str(), definition.c_str()); + } + + /** + * @param name unique name for the new prepared statement. + * @param definition SQL statement to prepare. + */ + void prepare(char const name[], char const definition[]) &; + + /// Define a nameless prepared statement. + /** + * This can be useful if you merely want to pass large binary parameters to a + * statement without otherwise wishing to prepare it. If you use this + * feature, always keep the definition and the use close together to avoid + * the nameless statement being redefined unexpectedly by code somewhere + * else. + */ + void prepare(char const definition[]) &; + void prepare(zview definition) & { return prepare(definition.c_str()); } + + /// Drop prepared statement. + void unprepare(std::string_view name); + + //@} + + // C++20: constexpr. Breaks ABI. + /// Suffix unique number to name to make it unique within session context. + /** Used internally to generate identifiers for SQL objects (such as cursors + * and nested transactions) based on a given human-readable base name. + */ + [[nodiscard]] std::string adorn_name(std::string_view); + + /** + * @defgroup escaping-functions String-escaping functions + */ + //@{ + + /// Escape string for use as SQL string literal on this connection. + /** @warning This accepts a length, and it does not require a terminating + * zero byte. But if there is a zero byte, escaping stops there even if + * it's not at the end of the string! + */ + [[deprecated("Use std::string_view or pqxx:zview.")]] std::string + esc(char const text[], std::size_t maxlen) const + { + return esc(std::string_view{text, maxlen}); + } + + /// Escape string for use as SQL string literal on this connection. + [[nodiscard]] std::string esc(char const text[]) const + { + return esc(std::string_view{text}); + } + +#if defined(PQXX_HAVE_SPAN) + /// Escape string for use as SQL string literal, into `buffer`. + /** Use this variant when you want to re-use the same buffer across multiple + * calls. If that's not the case, or convenience and simplicity are more + * important, use the single-argument variant. + * + * For every byte in `text`, there must be at least 2 bytes of space in + * `buffer`; plus there must be one byte of space for a trailing zero. + * Throws @ref range_error if this space is not available. + * + * Returns a reference to the escaped string, which is actually stored in + * `buffer`. + */ + [[nodiscard]] std::string_view + esc(std::string_view text, std::span buffer) + { + auto const size{std::size(text)}, space{std::size(buffer)}; + auto const needed{2 * size + 1}; + if (space < needed) + throw range_error{internal::concat( + "Not enough room to escape string of ", size, " byte(s): need ", + needed, " bytes of buffer space, but buffer size is ", space, ".")}; + auto const data{buffer.data()}; + return {data, esc_to_buf(text, data)}; + } +#endif + + /// Escape string for use as SQL string literal on this connection. + /** @warning This is meant for text strings only. It cannot contain bytes + * whose value is zero ("nul bytes"). + */ + [[nodiscard]] std::string esc(std::string_view text) const; + +#if defined(PQXX_HAVE_CONCEPTS) + /// Escape binary string for use as SQL string literal on this connection. + /** This is identical to `esc_raw(data)`. */ + template [[nodiscard]] std::string esc(DATA const &data) const + { + return esc_raw(data); + } +#endif + +#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) + /// Escape binary string for use as SQL string literal, into `buffer`. + /** Use this variant when you want to re-use the same buffer across multiple + * calls. If that's not the case, or convenience and simplicity are more + * important, use the single-argument variant. + * + * For every byte in `data`, there must be at least two bytes of space in + * `buffer`; plus there must be two bytes of space for a header and one for + * a trailing zero. Throws @ref range_error if this space is not available. + * + * Returns a reference to the escaped string, which is actually stored in + * `buffer`. + */ + template + [[nodiscard]] zview esc(DATA const &data, std::span buffer) const + { + auto const size{std::size(data)}, space{std::size(buffer)}; + auto const needed{internal::size_esc_bin(std::size(data))}; + if (space < needed) + throw range_error{internal::concat( + "Not enough room to escape binary string of ", size, " byte(s): need ", + needed, " bytes of buffer space, but buffer size is ", space, ".")}; + + std::basic_string_view view{std::data(data), std::size(data)}; + auto const out{std::data(buffer)}; + // Actually, in the modern format, we know beforehand exactly how many + // bytes we're going to fill. Just leave out the trailing zero. + internal::esc_bin(view, out); + return zview{out, needed - 1}; + } +#endif + + /// Escape binary string for use as SQL string literal on this connection. + [[deprecated("Use std::byte for binary data.")]] std::string + esc_raw(unsigned char const bin[], std::size_t len) const; + + /// Escape binary string for use as SQL string literal on this connection. + /** You can also just use @ref esc with a binary string. */ + [[nodiscard]] std::string esc_raw(std::basic_string_view) const; + +#if defined(PQXX_HAVE_SPAN) + /// Escape binary string for use as SQL string literal, into `buffer`. + /** You can also just use @ref esc with a binary string. */ + [[nodiscard]] std::string + esc_raw(std::basic_string_view, std::span buffer) const; +#endif + +#if defined(PQXX_HAVE_CONCEPTS) + /// Escape binary string for use as SQL string literal on this connection. + /** You can also just use @ref esc with a binary string. */ + template + [[nodiscard]] std::string esc_raw(DATA const &data) const + { + return esc_raw( + std::basic_string_view{std::data(data), std::size(data)}); + } +#endif + +#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN) + /// Escape binary string for use as SQL string literal, into `buffer`. + template + [[nodiscard]] zview esc_raw(DATA const &data, std::span buffer) const + { + return this->esc(binary_cast(data), buffer); + } +#endif + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string + unesc_raw(zview text) const + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return unesc_raw(text.c_str()); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string + unesc_raw(char const text[]) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + * + * (The data must be encoded in PostgreSQL's "hex" format. The legacy + * "bytea" escape format, used prior to PostgreSQL 9.0, is no longer + * supported.) + */ + [[nodiscard]] std::basic_string + unesc_bin(std::string_view text) const + { + std::basic_string buf; + buf.resize(pqxx::internal::size_unesc_bin(std::size(text))); + pqxx::internal::unesc_bin(text, buf.data()); + return buf; + } + + /// Escape and quote a string of binary data. + [[deprecated("Use quote(std::basic_string_view).")]] std::string + quote_raw(unsigned char const bin[], std::size_t len) const; + + /// Escape and quote a string of binary data. + std::string quote_raw(std::basic_string_view) const; + +#if defined(PQXX_HAVE_CONCEPTS) + /// Escape and quote a string of binary data. + /** You can also just use @ref quote with binary data. */ + template + [[nodiscard]] std::string quote_raw(DATA const &data) const + { + return quote_raw( + std::basic_string_view{std::data(data), std::size(data)}); + } +#endif + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote an SQL identifier for use in a query. + [[nodiscard]] std::string quote_name(std::string_view identifier) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote a table name. + /** When passing just a table name, this is just another name for + * @ref quote_name. + */ + [[nodiscard]] std::string quote_table(std::string_view name) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote a table path. + /** A table path consists of a table name, optionally prefixed by a schema + * name; and if both are given, they are in turn optionally prefixed by a + * database name. + * + * Each portion of the path (database name, schema name, table name) will be + * quoted separately, and they will be joined together by dots. So for + * example, `myschema.mytable` will become `"myschema"."mytable"`. + */ + [[nodiscard]] std::string quote_table(table_path) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Quote and comma-separate a series of column names. + /** Use this to save a bit of work in cases where you repeatedly need to pass + * the same list of column names, e.g. with @ref stream_to and @ref + * stream_from. Some functions that need to quote the columns list + * internally, will have a "raw" alternative which let you do the quoting + * yourself. It's a bit of extra work, but it can in rare cases let you + * eliminate some duplicate work in quoting them repeatedly. + */ + template + inline std::string quote_columns(STRINGS const &columns) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Represent object as SQL string, including quoting & escaping. + /** + * Recognises nulls and represents them as SQL nulls. They get no quotes. + */ + template + [[nodiscard]] inline std::string quote(T const &t) const; + + [[deprecated("Use std::byte for binary data.")]] std::string + quote(binarystring const &) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape and quote binary data for use as a BYTEA value in SQL statement. + [[nodiscard]] std::string + quote(std::basic_string_view bytes) const; + + // TODO: Make "into buffer" variant to eliminate a string allocation. + /// Escape string for literal LIKE match. + /** Use this when part of an SQL "LIKE" pattern should match only as a + * literal string, not as a pattern, even if it contains "%" or "_" + * characters that would normally act as wildcards. + * + * The string does not get string-escaped or quoted. You do that later. + * + * For instance, let's say you have a string `name` entered by the user, + * and you're searching a `file` column for items that match `name` + * followed by a dot and three letters. Even if `name` contains wildcard + * characters "%" or "_", you only want those to match literally, so "_" + * only matches "_" and "%" only matches a single "%". + * + * You do that by "like-escaping" `name`, appending the wildcard pattern + * `".___"`, and finally, escaping and quoting the result for inclusion in + * your query: + * + * ```cxx + * tx.exec( + * "SELECT file FROM item WHERE file LIKE " + + * tx.quote(tx.esc_like(name) + ".___")); + * ``` + * + * The SQL "LIKE" operator also lets you choose your own escape character. + * This is supported, but must be a single-byte character. + */ + [[nodiscard]] std::string + esc_like(std::string_view text, char escape_char = '\\') const; + //@} + + /// Attempt to cancel the ongoing query, if any. + /** You can use this from another thread, and/or while a query is executing + * in a pipeline, but it's up to you to ensure that you're not canceling the + * wrong query. This may involve locking. + */ + void cancel_query(); + +#if defined(_WIN32) || __has_include() + /// Set socket to blocking (true) or nonblocking (false). + /** @warning Do not use this unless you _really_ know what you're doing. + * @warning This function is available on most systems, but not necessarily + * all. + */ + void set_blocking(bool block) &; +#endif // defined(_WIN32) || __has_include() + + /// Set session verbosity. + /** Set the verbosity of error messages to "terse", "normal" (the default), + * or "verbose." + * + * If "terse", returned messages include severity, primary text, and + * position only; this will normally fit on a single line. "normal" produces + * messages that include the above plus any detail, hint, or context fields + * (these might span multiple lines). "verbose" includes all available + * fields. + */ + void set_verbosity(error_verbosity verbosity) &noexcept; + + /// Return pointers to the active errorhandlers. + /** The entries are ordered from oldest to newest handler. + * + * You may use this to find errorhandlers that your application wants to + * delete when destroying the connection. Be aware, however, that libpqxx + * may also add errorhandlers of its own, and those will be included in the + * list. If this is a problem for you, derive your errorhandlers from a + * custom base class derived from pqxx::errorhandler. Then use dynamic_cast + * to find which of the error handlers are yours. + * + * The pointers point to the real errorhandlers. The container it returns + * however is a copy of the one internal to the connection, not a reference. + */ + [[nodiscard]] std::vector get_errorhandlers() const; + + /// Return a connection string encapsulating this connection's options. + /** The connection must be currently open for this to work. + * + * Returns a reconstruction of this connection's connection string. It may + * not exactly match the connection string you passed in when creating this + * connection. + */ + [[nodiscard]] std::string connection_string() const; + + /// Explicitly close the connection. + /** The destructor will do this for you automatically. Still, there is a + * reason to `close()` objects explicitly where possible: if an error should + * occur while closing, `close()` can throw an exception. A destructor + * cannot. + * + * Closing a connection is idempotent. Closing a connection that's already + * closed does nothing. + */ + void close(); + + /// Seize control of a raw libpq connection. + /** @warning Do not do this. Please. It's for very rare, very specific + * use-cases. The mechanism may change (or break) in unexpected ways in + * future versions. + * + * @param raw_conn a raw libpq `PQconn` pointer. + */ + static connection seize_raw_connection(internal::pq::PGconn *raw_conn) + { + return connection{raw_conn}; + } + + /// Release the raw connection without closing it. + /** @warning Do not do this. It's for very rare, very specific use-cases. + * The mechanism may change (or break) in unexpected ways in future versions. + * + * The `connection` object becomes unusable after this. + */ + internal::pq::PGconn *release_raw_connection() && + { + return std::exchange(m_conn, nullptr); + } + +private: + friend class connecting; + enum connect_mode + { + connect_nonblocking + }; + connection(connect_mode, zview connection_string); + + /// For use by @ref seize_raw_connection. + explicit connection(internal::pq::PGconn *raw_conn) : m_conn{raw_conn} {} + + /// Poll for ongoing connection, try to progress towards completion. + /** Returns a pair of "now please wait to read data from socket" and "now + * please wait to write data to socket." Both will be false when done. + * + * Throws an exception if polling indicates that the connection has failed. + */ + std::pair poll_connect(); + + // Initialise based on connection string. + void init(char const options[]); + // Initialise based on parameter names and values. + void init(char const *params[], char const *values[]); + void complete_init(); + + result make_result( + internal::pq::PGresult *pgr, std::shared_ptr const &query, + std::string_view desc = ""sv); + + void PQXX_PRIVATE set_up_state(); + + int PQXX_PRIVATE PQXX_PURE status() const noexcept; + + /// Escape a string, into a buffer allocated by the caller. + /** The buffer must have room for at least `2*std::size(text) + 1` bytes. + * + * Returns the number of bytes written, including the trailing zero. + */ + std::size_t esc_to_buf(std::string_view text, char *buf) const; + + friend class internal::gate::const_connection_largeobject; + char const *PQXX_PURE err_msg() const noexcept; + + void PQXX_PRIVATE process_notice_raw(char const msg[]) noexcept; + + result exec_prepared(std::string_view statement, internal::c_params const &); + + /// Throw @ref usage_error if this connection is not in a movable state. + void check_movable() const; + /// Throw @ref usage_error if not in a state where it can be move-assigned. + void check_overwritable() const; + + friend class internal::gate::connection_errorhandler; + void PQXX_PRIVATE register_errorhandler(errorhandler *); + void PQXX_PRIVATE unregister_errorhandler(errorhandler *) noexcept; + + friend class internal::gate::connection_transaction; + result exec(std::string_view, std::string_view = ""sv); + result + PQXX_PRIVATE exec(std::shared_ptr, std::string_view = ""sv); + void PQXX_PRIVATE register_transaction(transaction_base *); + void PQXX_PRIVATE unregister_transaction(transaction_base *) noexcept; + + friend class internal::gate::connection_stream_from; + std::pair>, std::size_t> + PQXX_PRIVATE read_copy_line(); + + friend class internal::gate::connection_stream_to; + void PQXX_PRIVATE write_copy_line(std::string_view); + void PQXX_PRIVATE end_copy_write(); + + friend class internal::gate::connection_largeobject; + internal::pq::PGconn *raw_connection() const { return m_conn; } + + friend class internal::gate::connection_notification_receiver; + void add_receiver(notification_receiver *); + void remove_receiver(notification_receiver *) noexcept; + + friend class internal::gate::connection_pipeline; + void PQXX_PRIVATE start_exec(char const query[]); + bool PQXX_PRIVATE consume_input() noexcept; + bool PQXX_PRIVATE is_busy() const noexcept; + internal::pq::PGresult *get_result(); + + friend class internal::gate::connection_dbtransaction; + friend class internal::gate::connection_sql_cursor; + + result exec_params(std::string_view query, internal::c_params const &args); + + /// Connection handle. + internal::pq::PGconn *m_conn = nullptr; + + /// Active transaction on connection, if any. + /** We don't use this for anything, except to check for open transactions + * when we close the connection or start a new transaction. + * + * We also don't allow move construction or move assignment while there's a + * transaction, since moving the connection in that case would leave one or + * more pointers back from the transaction to the connection dangling. + */ + transaction_base const *m_trans = nullptr; + + std::list m_errorhandlers; + + using receiver_list = + std::multimap; + /// Notification receivers. + receiver_list m_receivers; + + /// Unique number to use as suffix for identifiers (see adorn_name()). + int m_unique_id = 0; +}; + + +/// @deprecated Old base class for connection. They are now the same class. +using connection_base = connection; + + +/// An ongoing, non-blocking stepping stone to a connection. +/** Use this when you want to create a connection to the database, but without + * blocking your whole thread. It is only available on systems that have + * the `` header, and Windows. + * + * Connecting in this way is probably not "faster" (it's more complicated and + * has some extra overhead), but in some situations you can use it to make your + * application as a whole faster. It all depends on having other useful work + * to do in the same thread, and being able to wait on a socket. If you have + * other I/O going on at the same time, your event loop can wait for both the + * libpqxx socket and your own sockets, and wake up whenever any of them is + * ready to do work. + * + * Connecting in this way is not properly "asynchronous;" it's merely + * "nonblocking." This means it's not a super-high-performance mechanism like + * you might get with e.g. `io_uring`. In particular, if we need to look up + * the database hostname in DNS, that will happen synchronously. + * + * To use this, create the `connecting` object, passing a connection string. + * Then loop: If @ref wait_to_read returns true, wait for the socket to have + * incoming data on it. If @ref wait_to_write returns true, wait for the + * socket to be ready for writing. Then call @ref process to process any + * incoming or outgoing data. Do all of this until @ref done returns true (or + * there is an exception). Finally, call @ref produce to get the completed + * connection. + * + * For example: + * + * ```cxx + * pqxx::connecting cg{}; + * + * // Loop until we're done connecting. + * while (!cg.done()) + * { + * wait_for_fd(cg.sock(), cg.wait_to_read(), cg.wait_to_write()); + * cg.process(); + * } + * + * pqxx::connection conn = std::move(cg).produce(); + * + * // At this point, conn is a working connection. You can no longer use + * // cg at all. + * ``` + */ +class PQXX_LIBEXPORT connecting +{ +public: + /// Start connecting. + connecting(zview connection_string = ""_zv); + + connecting(connecting const &) = delete; + connecting(connecting &&) = default; + connecting &operator=(connecting const &) = delete; + connecting &operator=(connecting &&) = default; + + /// Get the socket. The socket may change during the connection process. + [[nodiscard]] int sock() const &noexcept { return m_conn.sock(); } + + /// Should we currently wait to be able to _read_ from the socket? + [[nodiscard]] constexpr bool wait_to_read() const &noexcept + { + return m_reading; + } + + /// Should we currently wait to be able to _write_ to the socket? + [[nodiscard]] constexpr bool wait_to_write() const &noexcept + { + return m_writing; + } + + /// Progress towards completion (but don't block). + void process() &; + + /// Is our connection finished? + [[nodiscard]] constexpr bool done() const &noexcept + { + return not m_reading and not m_writing; + } + + /// Produce the completed connection object. + /** Use this only once, after @ref done returned `true`. Once you have + * called this, the `connecting` instance has no more use or meaning. You + * can't call any of its member functions afterwards. + * + * This member function is rvalue-qualified, meaning that you can only call + * it on an rvalue instance of the class. If what you have is not an rvalue, + * turn it into one by wrapping it in `std::move()`. + */ + [[nodiscard]] connection produce() &&; + +private: + connection m_conn; + bool m_reading{false}; + bool m_writing{true}; +}; + + +template inline std::string connection::quote(T const &t) const +{ + if constexpr (nullness::always_null) + { + return "NULL"; + } + else + { + if (is_null(t)) + return "NULL"; + auto const text{to_string(t)}; + + // Okay, there's an easy way to do this and there's a hard way. The easy + // way was "quote, esc(to_string(t)), quote". I'm going with the hard way + // because it's going to save some string manipulation that will probably + // incur some unnecessary memory allocations and deallocations. + std::string buf{'\''}; + buf.resize(2 + 2 * std::size(text) + 1); + auto const content_bytes{esc_to_buf(text, buf.data() + 1)}; + auto const closing_quote{1 + content_bytes}; + buf[closing_quote] = '\''; + auto const end{closing_quote + 1}; + buf.resize(end); + return buf; + } +} + + +template +inline std::string connection::quote_columns(STRINGS const &columns) const +{ + return separated_list( + ","sv, std::cbegin(columns), std::cend(columns), + [this](auto col) { return this->quote_name(*col); }); +} + + +#if defined(PQXX_HAVE_CONCEPTS) +template +inline connection::connection(MAPPING const ¶ms) +{ + check_version(); + + std::vector keys, values; + if constexpr (std::ranges::sized_range) + { + auto const size{std::ranges::size(params) + 1}; + keys.reserve(size); + values.reserve(size); + } + for (auto const &[key, value] : params) + { + keys.push_back(internal::as_c_string(key)); + values.push_back(internal::as_c_string(value)); + } + keys.push_back(nullptr); + values.push_back(nullptr); + init(std::data(keys), std::data(values)); +} +#endif // PQXX_HAVE_CONCEPTS +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor new file mode 100644 index 000000000..e20b3a4fa --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor @@ -0,0 +1,8 @@ +/** Definition of the iterator/container-style cursor classes. + * + * C++-style wrappers for SQL cursors + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/cursor.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor.hxx new file mode 100644 index 000000000..b392e2407 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/cursor.hxx @@ -0,0 +1,483 @@ +/* Definition of the iterator/container-style cursor classes. + * + * C++-style wrappers for SQL cursors. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/cursor instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_CURSOR +#define PQXX_H_CURSOR + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +#include "pqxx/result.hxx" +#include "pqxx/transaction_base.hxx" + + +namespace pqxx +{ +/// Common definitions for cursor types +/** In C++ terms, fetches are always done in pre-increment or pre-decrement + * fashion--i.e. the result does not include the row the cursor is on at the + * beginning of the fetch, and the cursor ends up being positioned on the last + * row in the result. + * + * There are singular positions akin to `end()` at both the beginning and the + * end of the cursor's range of movement, although these fit in so naturally + * with the semantics that one rarely notices them. The cursor begins at the + * first of these, but any fetch in the forward direction will move the cursor + * off this position and onto the first row before returning anything. + */ +class PQXX_LIBEXPORT cursor_base +{ +public: + using size_type = result_size_type; + using difference_type = result_difference_type; + + /// Cursor access-pattern policy + /** Allowing a cursor to move forward only can result in better performance, + * so use this access policy whenever possible. + */ + enum access_policy + { + /// Cursor can move forward only + forward_only, + /// Cursor can move back and forth + random_access + }; + + /// Cursor update policy + /** + * @warning Not all PostgreSQL versions support updatable cursors. + */ + enum update_policy + { + /// Cursor can be used to read data but not to write + read_only, + /// Cursor can be used to update data as well as read it + update + }; + + /// Cursor destruction policy + /** The normal thing to do is to make a cursor object the owner of the SQL + * cursor it represents. There may be cases, however, where a cursor needs + * to persist beyond the end of the current transaction (and thus also beyond + * the lifetime of the cursor object that created it!), where it can be + * "adopted" into a new cursor object. See the basic_cursor documentation + * for an explanation of cursor adoption. + * + * If a cursor is created with "loose" ownership policy, the object + * representing the underlying SQL cursor will not take the latter with it + * when its own lifetime ends, nor will its originating transaction. + * + * @warning Use this feature with care and moderation. Only one cursor + * object should be responsible for any one underlying SQL cursor at any + * given time. + */ + enum ownership_policy + { + /// Destroy SQL cursor when cursor object is closed at end of transaction + owned, + /// Leave SQL cursor in existence after close of object and transaction + loose + }; + + cursor_base() = delete; + cursor_base(cursor_base const &) = delete; + cursor_base &operator=(cursor_base const &) = delete; + + /** + * @name Special movement distances. + */ + //@{ + + // TODO: Make constexpr inline (but breaks ABI). + /// Special value: read until end. + /** @return Maximum value for result::difference_type, so the cursor will + * attempt to read the largest possible result set. + */ + [[nodiscard]] static difference_type all() noexcept; + + /// Special value: read one row only. + /** @return Unsurprisingly, 1. + */ + [[nodiscard]] static constexpr difference_type next() noexcept { return 1; } + + /// Special value: read backwards, one row only. + /** @return Unsurprisingly, -1. + */ + [[nodiscard]] static constexpr difference_type prior() noexcept + { + return -1; + } + + // TODO: Make constexpr inline (but breaks ABI). + /// Special value: read backwards from current position back to origin. + /** @return Minimum value for result::difference_type. + */ + [[nodiscard]] static difference_type backward_all() noexcept; + + //@} + + /// Name of underlying SQL cursor + /** + * @returns Name of SQL cursor, which may differ from original given name. + * @warning Don't use this to access the SQL cursor directly without going + * through the provided wrapper classes! + */ + [[nodiscard]] constexpr std::string const &name() const noexcept + { + return m_name; + } + +protected: + cursor_base(connection &, std::string_view Name, bool embellish_name = true); + + std::string const m_name; +}; +} // namespace pqxx + + +#include + + +namespace pqxx +{ +/// "Stateless cursor" class: easy API for retrieving parts of result sets +/** This is a front-end for SQL cursors, but with a more C++-like API. + * + * Actually, stateless_cursor feels entirely different from SQL cursors. You + * don't keep track of positions, fetches, and moves; you just say which rows + * you want. See the retrieve() member function. + */ +template +class stateless_cursor +{ +public: + using size_type = result_size_type; + using difference_type = result_difference_type; + + /// Create cursor. + /** + * @param tx The transaction within which you want to create the cursor. + * @param query The SQL query whose results the cursor should traverse. + * @param cname A hint for the cursor's name. The actual SQL cursor's name + * will be based on this (though not necessarily identical). + * @param hold Create a `WITH HOLD` cursor? Such cursors stay alive after + * the transaction has ended, so you can continue to use it. + */ + stateless_cursor( + transaction_base &tx, std::string_view query, std::string_view cname, + bool hold) : + m_cur{tx, query, cname, cursor_base::random_access, up, op, hold} + {} + + /// Adopt an existing scrolling SQL cursor. + /** This lets you define a cursor yourself, and then wrap it in a + * libpqxx-managed `stateless_cursor` object. + * + * @param tx The transaction within which you want to manage the cursor. + * @param adopted_cursor Your cursor's SQL name. + */ + stateless_cursor(transaction_base &tx, std::string_view adopted_cursor) : + m_cur{tx, adopted_cursor, op} + { + // Put cursor in known position + m_cur.move(cursor_base::backward_all()); + } + + /// Close this cursor. + /** The destructor will do this for you automatically. + * + * Closing a cursor is idempotent. Closing a cursor that's already closed + * does nothing. + */ + void close() noexcept { m_cur.close(); } + + /// Number of rows in cursor's result set + /** @note This function is not const; it may need to scroll to find the size + * of the result set. + */ + [[nodiscard]] size_type size() + { + return internal::obtain_stateless_cursor_size(m_cur); + } + + /// Retrieve rows from begin_pos (inclusive) to end_pos (exclusive) + /** Rows are numbered starting from 0 to size()-1. + * + * @param begin_pos First row to retrieve. May be one row beyond the end of + * the result set, to avoid errors for empty result sets. Otherwise, must be + * a valid row number in the result set. + * @param end_pos Row up to which to fetch. Rows are returned ordered from + * begin_pos to end_pos, i.e. in ascending order if begin_pos < end_pos but + * in descending order if begin_pos > end_pos. The end_pos may be + * arbitrarily inside or outside the result set; only existing rows are + * included in the result. + */ + result retrieve(difference_type begin_pos, difference_type end_pos) + { + return internal::stateless_cursor_retrieve( + m_cur, result::difference_type(size()), begin_pos, end_pos); + } + + /// Return this cursor's name. + [[nodiscard]] constexpr std::string const &name() const noexcept + { + return m_cur.name(); + } + +private: + internal::sql_cursor m_cur; +}; + + +class icursor_iterator; +} // namespace pqxx + + +namespace pqxx::internal::gate +{ +class icursor_iterator_icursorstream; +class icursorstream_icursor_iterator; +} // namespace pqxx::internal::gate + + +namespace pqxx +{ +/// Simple read-only cursor represented as a stream of results +/** SQL cursors can be tricky, especially in C++ since the two languages seem + * to have been designed on different planets. An SQL cursor has two singular + * positions akin to `end()` on either side of the underlying result set. + * + * These cultural differences are hidden from view somewhat by libpqxx, which + * tries to make SQL cursors behave more like familiar C++ entities such as + * iterators, sequences, streams, and containers. + * + * Data is fetched from the cursor as a sequence of result objects. Each of + * these will contain the number of rows defined as the stream's stride, except + * of course the last block of data which may contain fewer rows. + * + * This class can create or adopt cursors that live outside any backend + * transaction, which your backend version may not support. + */ +class PQXX_LIBEXPORT icursorstream +{ +public: + using size_type = cursor_base::size_type; + using difference_type = cursor_base::difference_type; + + /// Set up a read-only, forward-only cursor. + /** Roughly equivalent to a C++ Standard Library istream, this cursor type + * supports only two operations: reading a block of rows while moving + * forward, and moving forward without reading any data. + * + * @param context Transaction context in which this cursor will be active. + * @param query SQL query whose results this cursor shall iterate. + * @param basename Suggested name for the SQL cursor; the library will append + * a unique code to ensure its uniqueness. + * @param sstride Number of rows to fetch per read operation; must be a + * positive number. + */ + icursorstream( + transaction_base &context, std::string_view query, + std::string_view basename, difference_type sstride = 1); + + /// Adopt existing SQL cursor. Use with care. + /** Forms a cursor stream around an existing SQL cursor, as returned by e.g. + * a server-side function. The SQL cursor will be cleaned up by the stream's + * destructor as if it had been created by the stream; cleaning it up by hand + * or adopting the same cursor twice is an error. + * + * Passing the name of the cursor as a string is not allowed, both to avoid + * confusion with the other constructor and to discourage unnecessary use of + * adopted cursors. + * + * @warning It is technically possible to adopt a "WITH HOLD" cursor, i.e. a + * cursor that stays alive outside its creating transaction. However, any + * cursor stream (including the underlying SQL cursor, naturally) must be + * destroyed before its transaction context object is destroyed. Therefore + * the only way to use SQL's WITH HOLD feature is to adopt the cursor, but + * defer doing so until after entering the transaction context that will + * eventually destroy it. + * + * @param context Transaction context in which this cursor will be active. + * @param cname Result field containing the name of the SQL cursor to adopt. + * @param sstride Number of rows to fetch per read operation; must be a + * positive number. + * @param op Ownership policy. Determines whether the cursor underlying this + * stream will be destroyed when the stream is closed. + */ + icursorstream( + transaction_base &context, field const &cname, difference_type sstride = 1, + cursor_base::ownership_policy op = cursor_base::owned); + + /// Return `true` if this stream may still return more data. + constexpr operator bool() const &noexcept { return not m_done; } + + /// Read new value into given result object; same as operator `>>`. + /** The result set may continue any number of rows from zero to the chosen + * stride, inclusive. An empty result will only be returned if there are no + * more rows to retrieve. + * + * @param res Write the retrieved data into this result object. + * @return Reference to this very stream, to facilitate "chained" invocations + * ("C.get(r1).get(r2);") + */ + icursorstream &get(result &res) + { + res = fetchblock(); + return *this; + } + /// Read new value into given result object; same as `get(result&)`. + /** The result set may continue any number of rows from zero to the chosen + * stride, inclusive. An empty result will only be returned if there are no + * more rows to retrieve. + * + * @param res Write the retrieved data into this result object. + * @return Reference to this very stream, to facilitate "chained" invocations + * ("C >> r1 >> r2;") + */ + icursorstream &operator>>(result &res) { return get(res); } + + /// Move given number of rows forward without reading data. + /** Ignores any stride that you may have set. It moves by a given number of + * rows, not a number of strides. + * + * @return Reference to this stream itself, to facilitate "chained" + * invocations. + */ + icursorstream &ignore(std::streamsize n = 1) &; + + /// Change stride, i.e. the number of rows to fetch per read operation. + /** + * @param stride Must be a positive number. + */ + void set_stride(difference_type stride) &; + [[nodiscard]] constexpr difference_type stride() const noexcept + { + return m_stride; + } + +private: + result fetchblock(); + + friend class internal::gate::icursorstream_icursor_iterator; + size_type forward(size_type n = 1); + void insert_iterator(icursor_iterator *) noexcept; + void remove_iterator(icursor_iterator *) const noexcept; + + void service_iterators(difference_type); + + internal::sql_cursor m_cur; + + difference_type m_stride; + difference_type m_realpos, m_reqpos; + + mutable icursor_iterator *m_iterators; + + bool m_done; +}; + + +/// Approximate istream_iterator for icursorstream. +/** Intended as an implementation of an input_iterator (as defined by the C++ + * Standard Library), this class supports only two basic operations: reading + * the current element, and moving forward. In addition to the minimal + * guarantees for istream_iterators, this class supports multiple successive + * reads of the same position (the current result set is cached in the + * iterator) even after copying and even after new data have been read from the + * stream. This appears to be a requirement for input_iterators. Comparisons + * are also supported in the general case. + * + * The iterator does not care about its own position, however. Moving an + * iterator forward moves the underlying stream forward and reads the data from + * the new stream position, regardless of the iterator's old position in the + * stream. + * + * The stream's stride defines the granularity for all iterator movement or + * access operations, i.e. "ici += 1" advances the stream by one stride's worth + * of rows, and "*ici++" reads one stride's worth of rows from the stream. + * + * @warning Do not read from the underlying stream or its cursor, move its read + * position, or change its stride, between the time the first icursor_iterator + * on it is created and the time its last icursor_iterator is destroyed. + * + * @warning Manipulating these iterators within the context of a single cursor + * stream is not thread-safe. Creating a new iterator, copying one, + * or destroying one affects the stream as a whole. + */ +class PQXX_LIBEXPORT icursor_iterator +{ +public: + using iterator_category = std::input_iterator_tag; + using value_type = result; + using pointer = result const *; + using reference = result const &; + using istream_type = icursorstream; + using size_type = istream_type::size_type; + using difference_type = istream_type::difference_type; + + icursor_iterator() noexcept; + explicit icursor_iterator(istream_type &) noexcept; + icursor_iterator(icursor_iterator const &) noexcept; + ~icursor_iterator() noexcept; + + result const &operator*() const + { + refresh(); + return m_here; + } + result const *operator->() const + { + refresh(); + return &m_here; + } + icursor_iterator &operator++(); + icursor_iterator operator++(int); + icursor_iterator &operator+=(difference_type); + icursor_iterator &operator=(icursor_iterator const &) noexcept; + + [[nodiscard]] bool operator==(icursor_iterator const &rhs) const; + [[nodiscard]] bool operator!=(icursor_iterator const &rhs) const noexcept + { + return not operator==(rhs); + } + [[nodiscard]] bool operator<(icursor_iterator const &rhs) const; + [[nodiscard]] bool operator>(icursor_iterator const &rhs) const + { + return rhs < *this; + } + [[nodiscard]] bool operator<=(icursor_iterator const &rhs) const + { + return not(*this > rhs); + } + [[nodiscard]] bool operator>=(icursor_iterator const &rhs) const + { + return not(*this < rhs); + } + +private: + void refresh() const; + + friend class internal::gate::icursor_iterator_icursorstream; + difference_type pos() const noexcept { return m_pos; } + void fill(result const &); + + icursorstream *m_stream{nullptr}; + result m_here; + difference_type m_pos; + icursor_iterator *m_prev{nullptr}, *m_next{nullptr}; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction new file mode 100644 index 000000000..fa8d26476 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction @@ -0,0 +1,8 @@ +/** pqxx::dbtransaction abstract base class. + * + * pqxx::dbransaction defines a real transaction on the database. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/dbtransaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction.hxx new file mode 100644 index 000000000..d85cb170f --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/dbtransaction.hxx @@ -0,0 +1,70 @@ +/* Definition of the pqxx::dbtransaction abstract base class. + * + * pqxx::dbransaction defines a real transaction on the database. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/dbtransaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_DBTRANSACTION +#define PQXX_H_DBTRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/transaction_base.hxx" + +namespace pqxx +{ +/// Abstract transaction base class: bracket transactions on the database. +/** + * @ingroup transactions + * + * Use a dbtransaction-derived object such as "work" (transaction<>) to enclose + * operations on a database in a single "unit of work." This ensures that the + * whole series of operations either succeeds as a whole or fails completely. + * In no case will it leave half-finished work behind in the database. + * + * Once processing on a transaction has succeeded and any changes should be + * allowed to become permanent in the database, call commit(). If something + * has gone wrong and the changes should be forgotten, call abort() instead. + * If you do neither, an implicit abort() is executed at destruction time. + * + * It is an error to abort a transaction that has already been committed, or to + * commit a transaction that has already been aborted. Aborting an already + * aborted transaction or committing an already committed one is allowed, to + * make error handling easier. Repeated aborts or commits have no effect after + * the first one. + * + * Database transactions are not suitable for guarding long-running processes. + * If your transaction code becomes too long or too complex, consider ways to + * break it up into smaller ones. Unfortunately there is no universal recipe + * for this. + * + * The actual operations for committing/aborting the backend transaction are + * implemented by a derived class. The implementing concrete class must also + * call @ref close from its destructor. + */ +class PQXX_LIBEXPORT PQXX_NOVTABLE dbtransaction : public transaction_base +{ +protected: + /// Begin transaction. + explicit dbtransaction(connection &c) : transaction_base{c} {} + /// Begin transaction. + dbtransaction(connection &c, std::string_view tname) : + transaction_base{c, tname} + {} + /// Begin transaction. + dbtransaction( + connection &c, std::string_view tname, + std::shared_ptr rollback_cmd) : + transaction_base{c, tname, rollback_cmd} + {} +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler new file mode 100644 index 000000000..ea572ee79 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler @@ -0,0 +1,8 @@ +/** pqxx::errorhandler class. + * + * Callbacks for handling errors and warnings. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/errorhandler.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler.hxx new file mode 100644 index 000000000..2ffb5703c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/errorhandler.hxx @@ -0,0 +1,92 @@ +/* Definition of the pqxx::errorhandler class. + * + * pqxx::errorhandler handlers errors and warnings in a database session. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/errorhandler instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ERRORHANDLER +#define PQXX_H_ERRORHANDLER + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/types.hxx" + + +namespace pqxx::internal::gate +{ +class errorhandler_connection; +} + + +namespace pqxx +{ +/** + * @addtogroup errorhandler + */ +//@{ + +/// Base class for error-handler callbacks. +/** To receive errors and warnings from a connection, subclass this with your + * own error-handler functor, and instantiate it for the connection. Destroying + * the handler un-registers it. + * + * A connection can have multiple error handlers at the same time. When the + * database connection emits an error or warning message, it passes the message + * to each error handler, starting with the most recently registered one and + * progressing towards the oldest one. However an error handler may also + * instruct the connection not to pass the message to further handlers by + * returning "false." + * + * @warning Strange things happen when a result object outlives its parent + * connection. If you register an error handler on a connection, then you must + * not access the result after destroying the connection. This applies even if + * you destroy the error handler first! + */ +class PQXX_LIBEXPORT errorhandler +{ +public: + explicit errorhandler(connection &); + virtual ~errorhandler(); + + /// Define in subclass: receive an error or warning message from the + /// database. + /** + * @return Whether the same error message should also be passed to the + * remaining, older errorhandlers. + */ + virtual bool operator()(char const msg[]) noexcept = 0; + + errorhandler() = delete; + errorhandler(errorhandler const &) = delete; + errorhandler &operator=(errorhandler const &) = delete; + +private: + connection *m_home; + + friend class internal::gate::errorhandler_connection; + void unregister() noexcept; +}; + + +/// An error handler that suppresses any previously registered error handlers. +class quiet_errorhandler : public errorhandler +{ +public: + /// Suppress error notices. + quiet_errorhandler(connection &conn) : errorhandler{conn} {} + + /// Revert to previous handling of error notices. + virtual bool operator()(char const[]) noexcept override { return false; } +}; + +//@} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except new file mode 100644 index 000000000..e5dd508bf --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except @@ -0,0 +1,8 @@ +/** libpqxx exception classes. + * + * pqxx::sql_error, pqxx::broken_connection, pqxx::in_doubt_error, ... + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/except.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except.hxx new file mode 100644 index 000000000..24f959437 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/except.hxx @@ -0,0 +1,447 @@ +/* Definition of libpqxx exception classes. + * + * pqxx::sql_error, pqxx::broken_connection, pqxx::in_doubt_error, ... + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/except instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_EXCEPT +#define PQXX_H_EXCEPT + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + + +namespace pqxx +{ +/** + * @addtogroup exception Exception classes + * + * These exception classes follow, roughly, the two-level hierarchy defined by + * the PostgreSQL SQLSTATE error codes (see Appendix A of the PostgreSQL + * documentation corresponding to your server version). This is not a complete + * mapping though. There are other differences as well, e.g. the error code + * for `statement_completion_unknown` has a separate status in libpqxx as + * @ref in_doubt_error, and `too_many_connections` is classified as a + * `broken_connection` rather than a subtype of `insufficient_resources`. + * + * @see http://www.postgresql.org/docs/9.4/interactive/errcodes-appendix.html + * + * @{ + */ + +/// Run-time failure encountered by libpqxx, similar to std::runtime_error. +struct PQXX_LIBEXPORT failure : std::runtime_error +{ + explicit failure(std::string const &); +}; + + +/// Exception class for lost or failed backend connection. +/** + * @warning When this happens on Unix-like systems, you may also get a SIGPIPE + * signal. That signal aborts the program by default, so if you wish to be + * able to continue after a connection breaks, be sure to disarm this signal. + * + * If you're working on a Unix-like system, see the manual page for + * `signal` (2) on how to deal with SIGPIPE. The easiest way to make this + * signal harmless is to make your program ignore it: + * + * ```cxx + * #include + * + * int main() + * { + * signal(SIGPIPE, SIG_IGN); + * // ... + * ``` + */ +struct PQXX_LIBEXPORT broken_connection : failure +{ + broken_connection(); + explicit broken_connection(std::string const &); +}; + + +/// The caller attempted to set a variable to null, which is not allowed. +struct PQXX_LIBEXPORT variable_set_to_null : failure +{ + variable_set_to_null(); + explicit variable_set_to_null(std::string const &); +}; + + +/// Exception class for failed queries. +/** Carries, in addition to a regular error message, a copy of the failed query + * and (if available) the SQLSTATE value accompanying the error. + */ +class PQXX_LIBEXPORT sql_error : public failure +{ + /// Query string. Empty if unknown. + std::string const m_query; + /// SQLSTATE string describing the error type, if known; or empty string. + std::string const m_sqlstate; + +public: + explicit sql_error( + std::string const &whatarg = "", std::string const &Q = "", + char const sqlstate[] = nullptr); + virtual ~sql_error() noexcept override; + + /// The query whose execution triggered the exception + [[nodiscard]] PQXX_PURE std::string const &query() const noexcept; + + /// SQLSTATE error code if known, or empty string otherwise. + [[nodiscard]] PQXX_PURE std::string const &sqlstate() const noexcept; +}; + + +/// "Help, I don't know whether transaction was committed successfully!" +/** Exception that might be thrown in rare cases where the connection to the + * database is lost while finishing a database transaction, and there's no way + * of telling whether it was actually executed by the backend. In this case + * the database is left in an indeterminate (but consistent) state, and only + * manual inspection will tell which is the case. + */ +struct PQXX_LIBEXPORT in_doubt_error : failure +{ + explicit in_doubt_error(std::string const &); +}; + + +/// The backend saw itself forced to roll back the ongoing transaction. +struct PQXX_LIBEXPORT transaction_rollback : sql_error +{ + explicit transaction_rollback( + std::string const &whatarg, std::string const &q = "", + char const sqlstate[] = nullptr); +}; + + +/// Transaction failed to serialize. Please retry it. +/** Can only happen at transaction isolation levels REPEATABLE READ and + * SERIALIZABLE. + * + * The current transaction cannot be committed without violating the guarantees + * made by its isolation level. This is the effect of a conflict with another + * ongoing transaction. The transaction may still succeed if you try to + * perform it again. + */ +struct PQXX_LIBEXPORT serialization_failure : transaction_rollback +{ + explicit serialization_failure( + std::string const &whatarg, std::string const &q, + char const sqlstate[] = nullptr); +}; + + +/// We can't tell whether our last statement succeeded. +struct PQXX_LIBEXPORT statement_completion_unknown : transaction_rollback +{ + explicit statement_completion_unknown( + std::string const &whatarg, std::string const &q, + char const sqlstate[] = nullptr); +}; + + +/// The ongoing transaction has deadlocked. Retrying it may help. +struct PQXX_LIBEXPORT deadlock_detected : transaction_rollback +{ + explicit deadlock_detected( + std::string const &whatarg, std::string const &q, + char const sqlstate[] = nullptr); +}; + + +/// Internal error in libpqxx library +struct PQXX_LIBEXPORT internal_error : std::logic_error +{ + explicit internal_error(std::string const &); +}; + + +/// Error in usage of libpqxx library, similar to std::logic_error +struct PQXX_LIBEXPORT usage_error : std::logic_error +{ + explicit usage_error(std::string const &); +}; + + +/// Invalid argument passed to libpqxx, similar to std::invalid_argument +struct PQXX_LIBEXPORT argument_error : std::invalid_argument +{ + explicit argument_error(std::string const &); +}; + + +/// Value conversion failed, e.g. when converting "Hello" to int. +struct PQXX_LIBEXPORT conversion_error : std::domain_error +{ + explicit conversion_error(std::string const &); +}; + + +/// Could not convert value to string: not enough buffer space. +struct PQXX_LIBEXPORT conversion_overrun : conversion_error +{ + explicit conversion_overrun(std::string const &); +}; + + +/// Something is out of range, similar to std::out_of_range +struct PQXX_LIBEXPORT range_error : std::out_of_range +{ + explicit range_error(std::string const &); +}; + + +/// Query returned an unexpected number of rows. +struct PQXX_LIBEXPORT unexpected_rows : public range_error +{ + explicit unexpected_rows(std::string const &msg) : range_error{msg} {} +}; + + +/// Database feature not supported in current setup. +struct PQXX_LIBEXPORT feature_not_supported : sql_error +{ + explicit feature_not_supported( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +/// Error in data provided to SQL statement. +struct PQXX_LIBEXPORT data_exception : sql_error +{ + explicit data_exception( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT integrity_constraint_violation : sql_error +{ + explicit integrity_constraint_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT restrict_violation : integrity_constraint_violation +{ + explicit restrict_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT not_null_violation : integrity_constraint_violation +{ + explicit not_null_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT foreign_key_violation : integrity_constraint_violation +{ + explicit foreign_key_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT unique_violation : integrity_constraint_violation +{ + explicit unique_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT check_violation : integrity_constraint_violation +{ + explicit check_violation( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + integrity_constraint_violation{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT invalid_cursor_state : sql_error +{ + explicit invalid_cursor_state( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT invalid_sql_statement_name : sql_error +{ + explicit invalid_sql_statement_name( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT invalid_cursor_name : sql_error +{ + explicit invalid_cursor_name( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT syntax_error : sql_error +{ + /// Approximate position in string where error occurred, or -1 if unknown. + int const error_position; + + explicit syntax_error( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr, int pos = -1) : + sql_error{err, Q, sqlstate}, error_position{pos} + {} +}; + +struct PQXX_LIBEXPORT undefined_column : syntax_error +{ + explicit undefined_column( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + syntax_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT undefined_function : syntax_error +{ + explicit undefined_function( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + syntax_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT undefined_table : syntax_error +{ + explicit undefined_table( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + syntax_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT insufficient_privilege : sql_error +{ + explicit insufficient_privilege( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +/// Resource shortage on the server +struct PQXX_LIBEXPORT insufficient_resources : sql_error +{ + explicit insufficient_resources( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT disk_full : insufficient_resources +{ + explicit disk_full( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + insufficient_resources{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT out_of_memory : insufficient_resources +{ + explicit out_of_memory( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + insufficient_resources{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT too_many_connections : broken_connection +{ + explicit too_many_connections(std::string const &err) : + broken_connection{err} + {} +}; + +/// PL/pgSQL error +/** Exceptions derived from this class are errors from PL/pgSQL procedures. + */ +struct PQXX_LIBEXPORT plpgsql_error : sql_error +{ + explicit plpgsql_error( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + sql_error{err, Q, sqlstate} + {} +}; + +/// Exception raised in PL/pgSQL procedure +struct PQXX_LIBEXPORT plpgsql_raise : plpgsql_error +{ + explicit plpgsql_raise( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + plpgsql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT plpgsql_no_data_found : plpgsql_error +{ + explicit plpgsql_no_data_found( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + plpgsql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT plpgsql_too_many_rows : plpgsql_error +{ + explicit plpgsql_too_many_rows( + std::string const &err, std::string const &Q = "", + char const sqlstate[] = nullptr) : + plpgsql_error{err, Q, sqlstate} + {} +}; + +struct PQXX_LIBEXPORT blob_already_exists : failure +{ + explicit blob_already_exists(std::string const &); +}; + +/** + * @} + */ +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field new file mode 100644 index 000000000..37cb69e84 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field @@ -0,0 +1,8 @@ +/** pqxx::field class. + * + * pqxx::field refers to a field in a query result. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/field.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field.hxx new file mode 100644 index 000000000..b8b869fe4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/field.hxx @@ -0,0 +1,542 @@ +/* Definitions for the pqxx::field class. + * + * pqxx::field refers to a field in a query result. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/field instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_FIELD +#define PQXX_H_FIELD + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/array.hxx" +#include "pqxx/composite.hxx" +#include "pqxx/result.hxx" +#include "pqxx/strconv.hxx" +#include "pqxx/types.hxx" + +namespace pqxx +{ +/// Reference to a field in a result set. +/** A field represents one entry in a row. It represents an actual value + * in the result set, and can be converted to various types. + */ +class PQXX_LIBEXPORT field +{ +public: + using size_type = field_size_type; + + /// Constructor. Do not call this yourself; libpqxx will do it for you. + /** Create field as reference to a field in a result set. + * @param r Row that this field is part of. + * @param c Column number of this field. + */ + [[deprecated( + "Do not construct fields yourself. Get them from the row.")]] field(row const &r, row_size_type c) noexcept; + + /// Constructor. Do not call this yourself; libpqxx will do it for you. + [[deprecated( + "Do not construct fields yourself. Get them from the " + "row.")]] field() noexcept = default; + + /** + * @name Comparison + */ + //@{ + // TODO: noexcept. Breaks ABI. + /// Byte-by-byte comparison of two fields (all nulls are considered equal) + /** @warning null handling is still open to discussion and change! + * + * Handling of null values differs from that in SQL where a comparison + * involving a null value yields null, so nulls are never considered equal + * to one another or even to themselves. + * + * Null handling also probably differs from the closest equivalent in C++, + * which is the NaN (Not-a-Number) value, a singularity comparable to + * SQL's null. This is because the builtin == operator demands that a == a. + * + * The usefulness of this operator is questionable. No interpretation + * whatsoever is imposed on the data; 0 and 0.0 are considered different, + * as are null vs. the empty string, or even different (but possibly + * equivalent and equally valid) encodings of the same Unicode character + * etc. + */ + [[nodiscard]] PQXX_PURE bool operator==(field const &) const; + + /// Byte-by-byte comparison (all nulls are considered equal) + /** @warning See operator==() for important information about this operator + */ + [[nodiscard]] PQXX_PURE bool operator!=(field const &rhs) const noexcept + { + return not operator==(rhs); + } + //@} + + /** + * @name Column information + */ + //@{ + /// Column name. + [[nodiscard]] PQXX_PURE char const *name() const &; + + /// Column type. + [[nodiscard]] oid PQXX_PURE type() const; + + /// What table did this column come from? + [[nodiscard]] PQXX_PURE oid table() const; + + /// Return row number. The first row is row 0, the second is row 1, etc. + PQXX_PURE constexpr row_size_type num() const noexcept { return col(); } + + /// What column number in its originating table did this column come from? + [[nodiscard]] PQXX_PURE row_size_type table_column() const; + //@} + + /** + * @name Content access + */ + //@{ + /// Read as `string_view`, or an empty one if null. + /** The result only remains usable while the data for the underlying + * @ref result exists. Once all `result` objects referring to that data have + * been destroyed, the `string_view` will no longer point to valid memory. + */ + [[nodiscard]] PQXX_PURE std::string_view view() const & + { + return std::string_view(c_str(), size()); + } + + /// Read as plain C string. + /** Since the field's data is stored internally in the form of a + * zero-terminated C string, this is the fastest way to read it. Use the + * to() or as() functions to convert the string to other types such as + * `int`, or to C++ strings. + * + * Do not use this for BYTEA values, or other binary values. To read those, + * convert the value to your desired type using `to()` or `as()`. For + * example: `f.as>()`. + */ + [[nodiscard]] PQXX_PURE char const *c_str() const &; + + /// Is this field's value null? + [[nodiscard]] PQXX_PURE bool is_null() const noexcept; + + /// Return number of bytes taken up by the field's value. + [[nodiscard]] PQXX_PURE size_type size() const noexcept; + + /// Read value into obj; or if null, leave obj untouched and return `false`. + /** This can be used with optional types (except pointers other than C-style + * strings). + */ + template + auto to(T &obj) const -> typename std::enable_if_t< + (not std::is_pointer::value or std::is_same::value), + bool> + { + if (is_null()) + { + return false; + } + else + { + auto const bytes{c_str()}; + from_string(bytes, obj); + return true; + } + } + + /// Read field as a composite value, write its components into `fields`. + /** @warning This is still experimental. It may change or be replaced. + * + * Returns whether the field was null. If it was, it will not touch the + * values in `fields`. + */ + template bool composite_to(T &...fields) const + { + if (is_null()) + { + return false; + } + else + { + parse_composite(m_home.m_encoding, view(), fields...); + return true; + } + } + + /// Read value into obj; or leave obj untouched and return `false` if null. + template bool operator>>(T &obj) const { return to(obj); } + + /// Read value into obj; or if null, use default value and return `false`. + /** This can be used with `std::optional`, as well as with standard smart + * pointer types, but not with raw pointers. If the conversion from a + * PostgreSQL string representation allocates a pointer (e.g. using `new`), + * then the object's later deallocation should be baked in as well, right + * from the point where the object is created. So if you want a pointer, use + * a smart pointer, not a raw pointer. + * + * There is one exception, of course: C-style strings. Those are just + * pointers to the field's internal text data. + */ + template + auto to(T &obj, T const &default_value) const -> typename std::enable_if_t< + (not std::is_pointer::value or std::is_same::value), + bool> + { + bool const null{is_null()}; + if (null) + obj = default_value; + else + obj = from_string(this->view()); + return not null; + } + + /// Return value as object of given type, or default value if null. + /** Note that unless the function is instantiated with an explicit template + * argument, the Default value's type also determines the result type. + */ + template T as(T const &default_value) const + { + if (is_null()) + return default_value; + else + return from_string(this->view()); + } + + /// Return value as object of given type, or throw exception if null. + /** Use as `as>()` or `as()` as + * an alternative to `get()`; this is disabled for use with raw pointers + * (other than C-strings) because storage for the value can't safely be + * allocated here + */ + template T as() const + { + if (is_null()) + { + if constexpr (not nullness::has_null) + internal::throw_null_conversion(type_name); + else + return nullness::null(); + } + else + { + return from_string(this->view()); + } + } + + /// Return value wrapped in some optional type (empty for nulls). + /** Use as `get()` as before to obtain previous behavior, or specify + * container type with `get()` + */ + template class O = std::optional> + constexpr O get() const + { + return as>(); + } + + // TODO: constexpr noexcept, once array_parser constructor gets those. + /// Parse the field as an SQL array. + /** Call the parser to retrieve values (and structure) from the array. + * + * Make sure the @ref result object stays alive until parsing is finished. If + * you keep the @ref row of `field` object alive, it will keep the @ref + * result object alive as well. + */ + array_parser as_array() const & + { + return array_parser{c_str(), m_home.m_encoding}; + } + //@} + + +protected: + constexpr result const &home() const noexcept { return m_home; } + constexpr result::size_type idx() const noexcept { return m_row; } + constexpr row_size_type col() const noexcept { return m_col; } + + // TODO: Create gates. + friend class pqxx::result; + friend class pqxx::row; + field( + result const &r, result_size_type row_num, row_size_type col_num) noexcept + : + m_col{col_num}, m_home{r}, m_row{row_num} + {} + + /** + * You'd expect this to be unsigned, but due to the way reverse iterators + * are related to regular iterators, it must be allowed to underflow to -1. + */ + row_size_type m_col; + +private: + result m_home; + result::size_type m_row; +}; + + +template<> inline bool field::to(std::string &obj) const +{ + bool const null{is_null()}; + if (not null) + obj = std::string{view()}; + return not null; +} + + +template<> +inline bool field::to( + std::string &obj, std::string const &default_value) const +{ + bool const null{is_null()}; + if (null) + obj = default_value; + else + obj = std::string{view()}; + return not null; +} + + +/// Specialization: `to(char const *&)`. +/** The buffer has the same lifetime as the data in this result (i.e. of this + * result object, or the last remaining one copied from it etc.), so take care + * not to use it after the last result object referring to this query result is + * destroyed. + */ +template<> inline bool field::to(char const *&obj) const +{ + bool const null{is_null()}; + if (not null) + obj = c_str(); + return not null; +} + + +template<> inline bool field::to(std::string_view &obj) const +{ + bool const null{is_null()}; + if (not null) + obj = view(); + return not null; +} + + +template<> +inline bool field::to( + std::string_view &obj, std::string_view const &default_value) const +{ + bool const null{is_null()}; + if (null) + obj = default_value; + else + obj = view(); + return not null; +} + + +template<> inline std::string_view field::as() const +{ + if (is_null()) + PQXX_UNLIKELY + internal::throw_null_conversion(type_name); + return view(); +} + + +template<> +inline std::string_view +field::as(std::string_view const &default_value) const +{ + return is_null() ? default_value : view(); +} + + +template<> inline bool field::to(zview &obj) const +{ + bool const null{is_null()}; + if (not null) + obj = zview{c_str(), size()}; + return not null; +} + + +template<> +inline bool field::to(zview &obj, zview const &default_value) const +{ + bool const null{is_null()}; + if (null) + obj = default_value; + else + obj = zview{c_str(), size()}; + return not null; +} + + +template<> inline zview field::as() const +{ + if (is_null()) + PQXX_UNLIKELY + internal::throw_null_conversion(type_name); + return zview{c_str(), size()}; +} + + +template<> inline zview field::as(zview const &default_value) const +{ + return is_null() ? default_value : zview{c_str(), size()}; +} + + +template> +class field_streambuf : public std::basic_streambuf +{ +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + using openmode = std::ios::openmode; + using seekdir = std::ios::seekdir; + + explicit field_streambuf(field const &f) : m_field{f} { initialize(); } + +protected: + virtual int sync() override { return traits_type::eof(); } + + virtual pos_type seekoff(off_type, seekdir, openmode) override + { + return traits_type::eof(); + } + virtual pos_type seekpos(pos_type, openmode) override + { + return traits_type::eof(); + } + virtual int_type overflow(int_type) override { return traits_type::eof(); } + virtual int_type underflow() override { return traits_type::eof(); } + +private: + field const &m_field; + + int_type initialize() + { + auto g{static_cast(const_cast(m_field.c_str()))}; + this->setg(g, g, g + std::size(m_field)); + return int_type(std::size(m_field)); + } +}; + + +/// Input stream that gets its data from a result field +/** Use this class exactly as you would any other istream to read data from a + * field. All formatting and streaming operations of `std::istream` are + * supported. What you'll typically want to use, however, is the fieldstream + * alias (which defines a @ref basic_fieldstream for `char`). This is similar + * to how e.g. `std::ifstream` relates to `std::basic_ifstream`. + * + * This class has only been tested for the char type (and its default traits). + */ +template> +class basic_fieldstream : public std::basic_istream +{ + using super = std::basic_istream; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + + basic_fieldstream(field const &f) : super{nullptr}, m_buf{f} + { + super::init(&m_buf); + } + +private: + field_streambuf m_buf; +}; + +using fieldstream = basic_fieldstream; + +/// Write a result field to any type of stream +/** This can be convenient when writing a field to an output stream. More + * importantly, it lets you write a field to e.g. a `stringstream` which you + * can then use to read, format and convert the field in ways that to() does + * not support. + * + * Example: parse a field into a variable of the nonstandard + * "long long" type. + * + * ```cxx + * extern result R; + * long long L; + * stringstream S; + * + * // Write field's string into S + * S << R[0][0]; + * + * // Parse contents of S into L + * S >> L; + * ``` + */ +template +inline std::basic_ostream & +operator<<(std::basic_ostream &s, field const &value) +{ + s.write(value.c_str(), std::streamsize(std::size(value))); + return s; +} + + +/// Convert a field's value to type `T`. +/** Unlike the "regular" `from_string`, this knows how to deal with null + * values. + */ +template inline T from_string(field const &value) +{ + if (value.is_null()) + { + if constexpr (nullness::has_null) + return nullness::null(); + else + internal::throw_null_conversion(type_name); + } + else + { + return from_string(value.view()); + } +} + + +/// Convert a field's value to `nullptr_t`. +/** Yes, you read that right. This conversion does nothing useful. It always + * returns `nullptr`. + * + * Except... what if the field is not null? In that case, this throws + * @ref conversion_error. + */ +template<> +inline std::nullptr_t from_string(field const &value) +{ + if (not value.is_null()) + throw conversion_error{ + "Extracting non-null field into nullptr_t variable."}; + return nullptr; +} + + +/// Convert a field to a string. +template<> PQXX_LIBEXPORT std::string to_string(field const &value); +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/array-composite.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/array-composite.hxx new file mode 100644 index 000000000..d2b6603e5 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/array-composite.hxx @@ -0,0 +1,305 @@ +#if !defined(PQXX_ARRAY_COMPOSITE_HXX) +# define PQXX_ARRAY_COMPOSITE_HXX + +# include + +# include "pqxx/strconv.hxx" + +namespace pqxx::internal +{ +// Find the end of a double-quoted string. +/** `input[pos]` must be the opening double quote. + * + * Returns the offset of the first position after the closing quote. + */ +inline std::size_t scan_double_quoted_string( + char const input[], std::size_t size, std::size_t pos, + pqxx::internal::glyph_scanner_func *scan) +{ + // XXX: find_char<'"', '\\'>(). + auto next{scan(input, size, pos)}; + bool at_quote{false}; + for (pos = next, next = scan(input, size, pos); pos < size; + pos = next, next = scan(input, size, pos)) + { + if (at_quote) + { + if (next - pos == 1 and input[pos] == '"') + { + // We just read a pair of double quotes. Carry on. + at_quote = false; + } + else + { + // We just read one double quote, and now we're at a character that's + // not a second double quote. Ergo, that last character was the + // closing double quote and this is the position right after it. + return pos; + } + } + else if (next - pos == 1) + { + switch (input[pos]) + { + case '\\': + // Backslash escape. Skip ahead by one more character. + pos = next; + next = scan(input, size, pos); + break; + + case '"': + // This is either the closing double quote, or the first of a pair of + // double quotes. + at_quote = true; + break; + } + } + else + { + // Multibyte character. Carry on. + } + } + if (not at_quote) + throw argument_error{ + "Missing closing double-quote: " + std::string{input}}; + return pos; +} + + +/// Un-quote and un-escape a double-quoted SQL string. +inline std::string parse_double_quoted_string( + char const input[], std::size_t end, std::size_t pos, + pqxx::internal::glyph_scanner_func *scan) +{ + std::string output; + // Maximum output size is same as the input size, minus the opening and + // closing quotes. Or in the extreme opposite case, the real number could be + // half that. Usually it'll be a pretty close estimate. + output.reserve(std::size_t(end - pos - 2)); + + for (auto here{scan(input, end, pos)}, next{scan(input, end, here)}; + here < end - 1; here = next, next = scan(input, end, here)) + { + // A backslash here is always an escape. So is a double-quote, since we're + // inside the double-quoted string. In either case, we can just ignore the + // escape character and use the next character. This is the one redeeming + // feature of SQL's escaping system. + if ((next - here == 1) and (input[here] == '\\' or input[here] == '"')) + { + // Skip escape. + here = next; + next = scan(input, end, here); + } + output.append(input + here, input + next); + } + return output; +} + + +/// Find the end of an unquoted string in an array or composite-type value. +/** Stops when it gets to the end of the input; or when it sees any of the + * characters in STOP which has not been escaped. + * + * For array values, STOP is a comma, a semicolon, or a closing brace. For + * a value of a composite type, STOP is a comma or a closing parenthesis. + */ +template +inline std::size_t scan_unquoted_string( + char const input[], std::size_t size, std::size_t pos, + pqxx::internal::glyph_scanner_func *scan) +{ + bool at_backslash{false}; + auto next{scan(input, size, pos)}; + while ((pos < size) and + ((next - pos) > 1 or at_backslash or ((input[pos] != STOP) and ...))) + { + pos = next; + next = scan(input, size, pos); + at_backslash = + ((not at_backslash) and ((next - pos) == 1) and (input[pos] == '\\')); + } + return pos; +} + + +/// Parse an unquoted array entry or cfield of a composite-type field. +inline std::string parse_unquoted_string( + char const input[], std::size_t end, std::size_t pos, + pqxx::internal::glyph_scanner_func *scan) +{ + std::string output; + bool at_backslash{false}; + output.reserve(end - pos); + for (auto next{scan(input, end, pos)}; pos < end; + pos = next, next = scan(input, end, pos)) + { + at_backslash = + ((not at_backslash) and ((next - pos) == 1) and (input[pos] == '\\')); + if (not at_backslash) + output.append(input + pos, next - pos); + } + return output; +} + + +/// Parse a field of a composite-type value. +/** `T` is the C++ type of the field we're parsing, and `index` is its + * zero-based number. + * + * Strip off the leading parenthesis or bracket yourself before parsing. + * However, this function will parse the lcosing parenthesis or bracket. + * + * After a successful parse, `pos` will point at `std::end(text)`. + * + * For the purposes of parsing, ranges and arrays count as compositve values, + * so this function supports parsing those. If you specifically need a closing + * parenthesis, check afterwards that `text` did not end in a bracket instead. + * + * @param index Index of the current field, zero-based. It will increment for + * the next field. + * @param input Full input text for the entire composite-type value. + * @param pos Starting position (in `input`) of the field that we're parsing. + * After parsing, this will point at the beginning of the next field if + * there is one, or one position past the last character otherwise. + * @param field Destination for the parsed value. + * @param scan Glyph scanning function for the relevant encoding type. + * @param last_field Number of the last field in the value (zero-based). When + * parsing the last field, this will equal `index`. + */ +template +inline void parse_composite_field( + std::size_t &index, std::string_view input, std::size_t &pos, T &field, + glyph_scanner_func *scan, std::size_t last_field) +{ + assert(index <= last_field); + auto next{scan(std::data(input), std::size(input), pos)}; + if ((next - pos) != 1) + throw conversion_error{"Non-ASCII character in composite-type syntax."}; + + // Expect a field. + switch (input[pos]) + { + case ',': + case ')': + case ']': + // The field is empty, i.e, null. + if constexpr (nullness::has_null) + field = nullness::null(); + else + throw conversion_error{ + "Can't read composite field " + to_string(index) + ": C++ type " + + type_name + " does not support nulls."}; + break; + + case '"': { + auto const stop{scan_double_quoted_string( + std::data(input), std::size(input), pos, scan)}; + auto const text{ + parse_double_quoted_string(std::data(input), stop, pos, scan)}; + field = from_string(text); + pos = stop; + } + break; + + default: { + auto const stop{scan_unquoted_string<',', ')', ']'>( + std::data(input), std::size(input), pos, scan)}; + auto const text{parse_unquoted_string(std::data(input), stop, pos, scan)}; + field = from_string(text); + pos = stop; + } + break; + } + + // Expect a comma or a closing parenthesis. + next = scan(std::data(input), std::size(input), pos); + + if ((next - pos) != 1) + throw conversion_error{ + "Unexpected non-ASCII character after composite field: " + + std::string{input}}; + + if (index < last_field) + { + if (input[pos] != ',') + throw conversion_error{ + "Found '" + std::string{input[pos]} + + "' in composite value where comma was expected: " + std::data(input)}; + } + else + { + if (input[pos] == ',') + throw conversion_error{ + "Composite value contained more fields than the expected " + + to_string(last_field) + ": " + std::data(input)}; + if (input[pos] != ')' and input[pos] != ']') + throw conversion_error{ + "Composite value has unexpected characters where closing parenthesis " + "was expected: " + + std::string{input}}; + if (next != std::size(input)) + throw conversion_error{ + "Composite value has unexpected text after closing parenthesis: " + + std::string{input}}; + } + + pos = next; + ++index; +} + + +/// Conservatively estimate buffer size needed for a composite field. +template +inline std::size_t size_composite_field_buffer(T const &field) +{ + if constexpr (is_unquoted_safe) + { + // Safe to copy, without quotes or escaping. Drop the terminating zero. + return size_buffer(field) - 1; + } + else + { + // + Opening quote. + // + Field budget. + // - Terminating zero. + // + Escaping for each byte in the field's string representation. + // - Escaping for terminating zero. + // + Closing quote. + return 1 + 2 * (size_buffer(field) - 1) + 1; + } +} + + +template +inline void write_composite_field(char *&pos, char *end, T const &field) +{ + if constexpr (is_unquoted_safe) + { + // No need for quoting or escaping. Convert it straight into its final + // place in the buffer, and "backspace" the trailing zero. + pos = string_traits::into_buf(pos, end, field) - 1; + } + else + { + // The field may need escaping, which means we need an intermediate buffer. + // To avoid allocating that at run time, we use the end of the buffer that + // we have. + auto const budget{size_buffer(field)}; + *pos++ = '"'; + + // Now escape buf into its final position. + for (char const c : string_traits::to_buf(end - budget, end, field)) + { + if ((c == '"') or (c == '\\')) + *pos++ = '\\'; + + *pos++ = c; + } + + *pos++ = '"'; + } + + *pos++ = ','; +} +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/callgate.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/callgate.hxx new file mode 100644 index 000000000..42f7703e3 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/callgate.hxx @@ -0,0 +1,70 @@ +#ifndef PQXX_H_CALLGATE +#define PQXX_H_CALLGATE + +/* +Here's what a typical gate class definition looks like: + +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE @gateclass@ : callgate<@host@> +{ + friend class @client@; + + @gateclass@(reference x) : super(x) {} + + // Methods here. Use home() to access the host-class object. +}; +} // namespace pqxx::internal::gate +*/ + +namespace pqxx::internal +{ +/// Base class for call gates. +/** + * A call gate defines a limited, private interface on the host class that + * specified client classes can access. + * + * The metaphor works as follows: the gate stands in front of a "home," which + * is really a class, and only lets specific friends in. + * + * To implement a call gate that gives client C access to host H, + * * derive a gate class from callgate; + * * make the gate class a friend of H; + * * make C a friend of the gate class; and + * * implement "stuff C can do with H" as private members in the gate class. + * + * This special kind of "gated" friendship gives C private access to H, but + * only through an expressly limited interface. The gate class can access its + * host object as home(). + * + * Keep gate classes entirely stateless. They should be ultra-lightweight + * wrappers for their host classes, and be optimized away as much as possible + * by the compiler. Once you start adding state, you're on a slippery slope + * away from the pure, clean, limited interface pattern that gate classes are + * meant to implement. + * + * Ideally, all member functions of the gate class should be one-liners passing + * calls straight on to the host class. It can be useful however to break this + * rule temporarily during inter-class refactoring. + */ +template class PQXX_PRIVATE callgate +{ +protected: + /// This class, to keep constructors easy. + using super = callgate; + /// A reference to the host class. Helps keep constructors easy. + using reference = HOME &; + + callgate(reference x) : m_home(x) {} + + /// The home object. The gate class has full "private" access. + reference home() const noexcept { return m_home; } + +private: + reference m_home; +}; +} // namespace pqxx::internal + +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/concat.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/concat.hxx new file mode 100644 index 000000000..cd28bde7c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/concat.hxx @@ -0,0 +1,45 @@ +#if !defined(PQXX_CONCAT_HXX) +# define PQXX_CONCAT_HXX + +# include +# include + +# include "pqxx/strconv.hxx" + +namespace pqxx::internal +{ +/// Convert item to a string, write it into [here, end). +template +void render_item(TYPE const &item, char *&here, char *end) +{ + here = string_traits::into_buf(here, end, item) - 1; +} + + +// C++20: Support non-random_access_range ranges. +/// Efficiently combine a bunch of items into one big string. +/** Use this as an optimised version of string concatentation. It takes just + * about any type; it will represent each item as a string according to its + * @ref string_traits. + * + * This is a simpler, more specialised version of @ref separated_list for a + * statically known series of items, possibly of different types. + */ +template +[[nodiscard]] inline std::string concat(TYPE... item) +{ + std::string buf; + // Size to accommodate string representations of all inputs, minus their + // terminating zero bytes. + buf.resize(size_buffer(item...)); + + char *const data{buf.data()}; + char *here = data; + char *end = data + std::size(buf); + (render_item(item, here, end), ...); + + buf.resize(static_cast(here - data)); + return buf; +} +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/conversions.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/conversions.hxx new file mode 100644 index 000000000..1df4fdead --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/conversions.hxx @@ -0,0 +1,1188 @@ +#include +#include +#include +#include +#include +#include + +#if defined(PQXX_HAVE_SPAN) && __has_include() +# include +#endif + +#include +#include +#include + +#include "pqxx/types.hxx" +#include "pqxx/util.hxx" + + +/* Internal helpers for string conversion, and conversion implementations. + * + * Do not include this header directly. The libpqxx headers do it for you. + */ +namespace pqxx::internal +{ +/// Convert a number in [0, 9] to its ASCII digit. +inline constexpr char number_to_digit(int i) noexcept +{ + return static_cast(i + '0'); +} + + +/// Compute numeric value of given textual digit (assuming that it is a digit). +constexpr int digit_to_number(char c) noexcept +{ + return c - '0'; +} + + +/// Summarize buffer overrun. +/** Don't worry about the exact parameter types: the sizes will be reasonably + * small, and nonnegative. + */ +std::string PQXX_LIBEXPORT +state_buffer_overrun(int have_bytes, int need_bytes); + + +template +inline std::string state_buffer_overrun(HAVE have_bytes, NEED need_bytes) +{ + return state_buffer_overrun( + static_cast(have_bytes), static_cast(need_bytes)); +} + + +/// Throw exception for attempt to convert null to given type. +[[noreturn]] PQXX_LIBEXPORT void +throw_null_conversion(std::string const &type); + + +/// Deliberately nonfunctional conversion traits for `char` types. +/** There are no string conversions for `char` and its signed and unsigned + * variants. Such a conversion would be dangerously ambiguous: should we treat + * it as text, or as a small integer? It'd be an open invitation for bugs. + * + * But the error message when you get this wrong is very cryptic. So, we + * derive dummy @ref string_traits implementations from this dummy type, and + * ensure that the compiler disallows their use. The compiler error message + * will at least contain a hint of the root of the problem. + */ +template struct disallowed_ambiguous_char_conversion +{ + static char *into_buf(char *, char *, CHAR_TYPE) = delete; + static constexpr zview + to_buf(char *, char *, CHAR_TYPE const &) noexcept = delete; + + static constexpr std::size_t + size_buffer(CHAR_TYPE const &) noexcept = delete; + static CHAR_TYPE from_string(std::string_view) = delete; +}; + + +template PQXX_LIBEXPORT extern std::string to_string_float(T); + + +/// Generic implementation for into_buf, on top of to_buf. +template +inline char *generic_into_buf(char *begin, char *end, T const &value) +{ + zview const text{string_traits::to_buf(begin, end, value)}; + auto const space{end - begin}; + // Include the trailing zero. + auto const len = std::size(text) + 1; + if (internal::cmp_greater(len, space)) + throw conversion_overrun{ + "Not enough buffer space to insert " + type_name + ". " + + state_buffer_overrun(space, len)}; + std::memmove(begin, text.data(), len); + return begin + len; +} + + +/// String traits for builtin integral types (though not bool). +template struct integral_traits +{ + static PQXX_LIBEXPORT T from_string(std::string_view text); + static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value); + static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value); + + static constexpr std::size_t size_buffer(T const &) noexcept + { + /** Includes a sign if needed; the number of base-10 digits which the type + * can reliably represent; the one extra base-10 digit which the type can + * only partially represent; and the terminating zero. + */ + return std::is_signed_v + std::numeric_limits::digits10 + 1 + 1; + } +}; + + +/// String traits for builtin floating-point types. +template struct float_traits +{ + static PQXX_LIBEXPORT T from_string(std::string_view text); + static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value); + static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value); + + // Return a nonnegative integral value's number of decimal digits. + static constexpr std::size_t digits10(std::size_t value) noexcept + { + if (value < 10) + return 1; + else + return 1 + digits10(value / 10); + } + + static constexpr std::size_t size_buffer(T const &) noexcept + { + using lims = std::numeric_limits; + // See #328 for a detailed discussion on the maximum number of digits. + // + // In a nutshell: for the big cases, the scientific notation is always + // the shortest one, and therefore the one that to_chars will pick. + // + // So... How long can the scientific notation get? 1 (for sign) + 1 (for + // decimal point) + 1 (for 'e') + 1 (for exponent sign) + max_digits10 + + // max number of digits in the exponent + 1 (terminating zero). + // + // What's the max number of digits in the exponent? It's the max number of + // digits out of the most negative exponent and the most positive one. + // + // The longest positive exponent is easy: 1 + ceil(log10(max_exponent10)). + // (The extra 1 is because 10^n takes up 1 + n digits, not n.) + // + // The longest negative exponent is a bit harder: min_exponent10 gives us + // the smallest power of 10 which a normalised version of T can represent. + // But the smallest denormalised power of 10 that T can represent is + // another max_digits10 powers of 10 below that. + // needs a minus sign. + // + // All this stuff messes with my head a bit because it's on the order of + // log10(log10(n)). It's easy to get the number of logs wrong. + auto const max_pos_exp{digits10(lims::max_exponent10)}; + // Really want std::abs(lims::min_exponent10), but MSVC 2017 apparently has + // problems with std::abs. So we use -lims::min_exponent10 instead. + auto const max_neg_exp{ + digits10(lims::max_digits10 - lims::min_exponent10)}; + return 1 + // Sign. + 1 + // Decimal point. + std::numeric_limits::max_digits10 + // Mantissa digits. + 1 + // Exponent "e". + 1 + // Exponent sign. + // Spell this weirdly to stop Windows compilers from reading this as + // a call to their "max" macro when NOMINMAX is not defined. + (std::max)(max_pos_exp, max_neg_exp) + // Exponent digits. + 1; // Terminating zero. + } +}; +} // namespace pqxx::internal + + +namespace pqxx +{ +/// The built-in arithmetic types do not have inherent null values. +template +struct nullness>> : no_null +{}; + + +template<> struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits + : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits + : internal::integral_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::float_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> struct string_traits : internal::float_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; +template<> +struct string_traits : internal::float_traits +{}; +template<> inline constexpr bool is_unquoted_safe{true}; + + +template<> struct string_traits +{ + static PQXX_LIBEXPORT bool from_string(std::string_view text); + + static constexpr zview to_buf(char *, char *, bool const &value) noexcept + { + return value ? "true"_zv : "false"_zv; + } + + static char *into_buf(char *begin, char *end, bool const &value) + { + return pqxx::internal::generic_into_buf(begin, end, value); + } + + static constexpr std::size_t size_buffer(bool const &) noexcept { return 6; } +}; + + +/// We don't support conversion to/from `char` types. +/** Why are these disallowed? Because they are ambiguous. It's not inherently + * clear whether we should treat values of these types as text or as small + * integers. Either choice would lead to bugs. + */ +template<> +struct string_traits + : internal::disallowed_ambiguous_char_conversion +{}; + +/// We don't support conversion to/from `char` types. +/** Why are these disallowed? Because they are ambiguous. It's not inherently + * clear whether we should treat values of these types as text or as small + * integers. Either choice would lead to bugs. + */ +template<> +struct string_traits + : internal::disallowed_ambiguous_char_conversion +{}; + +/// We don't support conversion to/from `char` types. +/** Why are these disallowed? Because they are ambiguous. It's not inherently + * clear whether we should treat values of these types as text or as small + * integers. Either choice would lead to bugs. + */ +template<> +struct string_traits + : internal::disallowed_ambiguous_char_conversion +{}; + + +template<> inline constexpr bool is_unquoted_safe{true}; + + +template struct nullness> +{ + static constexpr bool has_null = true; + /// Technically, you could have an optional of an always-null type. + static constexpr bool always_null = nullness::always_null; + static constexpr bool is_null(std::optional const &v) noexcept + { + return ((not v.has_value()) or pqxx::is_null(*v)); + } + static constexpr std::optional null() { return {}; } +}; + + +template +inline constexpr format param_format(std::optional const &value) +{ + return param_format(*value); +} + + +template struct string_traits> +{ + static char *into_buf(char *begin, char *end, std::optional const &value) + { + return string_traits::into_buf(begin, end, *value); + } + + static zview to_buf(char *begin, char *end, std::optional const &value) + { + if (value.has_value()) + return string_traits::to_buf(begin, end, *value); + else + return {}; + } + + static std::optional from_string(std::string_view text) + { + return std::optional{ + std::in_place, string_traits::from_string(text)}; + } + + static std::size_t size_buffer(std::optional const &value) noexcept + { + return pqxx::size_buffer(value.value()); + } +}; + + +template +inline constexpr bool is_unquoted_safe>{is_unquoted_safe}; + + +template struct nullness> +{ + static constexpr bool has_null = (nullness::has_null or ...); + static constexpr bool always_null = (nullness::always_null and ...); + static constexpr bool is_null(std::variant const &value) noexcept + { + return std::visit( + [](auto const &i) noexcept { + return nullness>::is_null(i); + }, + value); + } + + // We don't support `null()` for `std::variant`. + /** It would be technically possible to have a `null` in the case where just + * one of the types has a null, but it gets complicated and arbitrary. + */ + static constexpr std::variant null() = delete; +}; + + +template struct string_traits> +{ + static char * + into_buf(char *begin, char *end, std::variant const &value) + { + return std::visit( + [begin, end](auto const &i) { + return string_traits>::into_buf(begin, end, i); + }, + value); + } + static zview to_buf(char *begin, char *end, std::variant const &value) + { + return std::visit( + [begin, end](auto const &i) { + return string_traits>::to_buf(begin, end, i); + }, + value); + } + static std::size_t size_buffer(std::variant const &value) noexcept + { + return std::visit( + [](auto const &i) noexcept { return pqxx::size_buffer(i); }, value); + } + + /** There's no from_string for std::variant. We could have one with a rule + * like "pick the first type which fits the value," but we'd have to look + * into how natural that API feels to users. + */ + static std::variant from_string(std::string_view) = delete; +}; + + +template +inline constexpr format param_format(std::variant const &value) +{ + return std::visit([](auto &v) { return param_format(v); }, value); +} + + +template +inline constexpr bool is_unquoted_safe>{ + (is_unquoted_safe and ...)}; + + +template inline T from_string(std::stringstream const &text) +{ + return from_string(text.str()); +} + + +template<> struct string_traits +{ + static char *into_buf(char *, char *, std::nullptr_t) = delete; + + static constexpr zview + to_buf(char *, char *, std::nullptr_t const &) noexcept + { + return {}; + } + + static constexpr std::size_t size_buffer(std::nullptr_t = nullptr) noexcept + { + return 0; + } + static std::nullptr_t from_string(std::string_view) = delete; +}; + + +template<> struct string_traits +{ + static char *into_buf(char *, char *, std::nullopt_t) = delete; + + static constexpr zview + to_buf(char *, char *, std::nullopt_t const &) noexcept + { + return {}; + } + + static constexpr std::size_t size_buffer(std::nullopt_t) noexcept + { + return 0; + } + static std::nullopt_t from_string(std::string_view) = delete; +}; + + +template<> struct string_traits +{ + static char *into_buf(char *, char *, std::monostate) = delete; + + static constexpr zview + to_buf(char *, char *, std::monostate const &) noexcept + { + return {}; + } + + static constexpr std::size_t size_buffer(std::monostate) noexcept + { + return 0; + } + static std::monostate from_string(std::string_view) = delete; +}; + + +template<> inline constexpr bool is_unquoted_safe{true}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = false; + static constexpr bool is_null(char const *t) noexcept + { + return t == nullptr; + } + static constexpr char const *null() noexcept { return nullptr; } +}; + + +/// String traits for C-style string ("pointer to char const"). +template<> struct string_traits +{ + static char const *from_string(std::string_view text) { return text.data(); } + + static zview to_buf(char *begin, char *end, char const *const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf(char *begin, char *end, char const *const &value) + { + auto const space{end - begin}; + // Count the trailing zero, even though std::strlen() and friends don't. + auto const len{std::strlen(value) + 1}; + if (space < ptrdiff_t(len)) + throw conversion_overrun{ + "Could not copy string: buffer too small. " + + pqxx::internal::state_buffer_overrun(space, len)}; + std::memmove(begin, value, len); + return begin + len; + } + + static std::size_t size_buffer(char const *const &value) noexcept + { + return std::strlen(value) + 1; + } +}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = false; + static constexpr bool is_null(char const *t) noexcept + { + return t == nullptr; + } + static constexpr char const *null() { return nullptr; } +}; + + +/// String traits for non-const C-style string ("pointer to char"). +template<> struct string_traits +{ + static char *into_buf(char *begin, char *end, char *const &value) + { + return string_traits::into_buf(begin, end, value); + } + static zview to_buf(char *begin, char *end, char *const &value) + { + return string_traits::to_buf(begin, end, value); + } + static std::size_t size_buffer(char *const &value) noexcept + { + return string_traits::size_buffer(value); + } + + /// Don't allow conversion to this type since it breaks const-safety. + static char *from_string(std::string_view) = delete; +}; + + +template struct nullness : no_null +{}; + + +/// String traits for C-style string constant ("array of char"). +/** @warning This assumes that every array-of-char is a C-style string literal. + * So, it must include a trailing zero. and it must have static duration. + */ +template struct string_traits +{ + static constexpr zview + to_buf(char *, char *, char const (&value)[N]) noexcept + { + return zview{value, N - 1}; + } + + static char *into_buf(char *begin, char *end, char const (&value)[N]) + { + if (internal::cmp_less(end - begin, size_buffer(value))) + throw conversion_overrun{ + "Could not convert char[] to string: too long for buffer."}; + std::memcpy(begin, value, N); + return begin + N; + } + static constexpr std::size_t size_buffer(char const (&)[N]) noexcept + { + return N; + } + + /// Don't allow conversion to this type. + static void from_string(std::string_view) = delete; +}; + + +template<> struct nullness : no_null +{}; + + +template<> struct string_traits +{ + static std::string from_string(std::string_view text) + { + return std::string{text}; + } + + static char *into_buf(char *begin, char *end, std::string const &value) + { + if (internal::cmp_greater_equal(std::size(value), end - begin)) + throw conversion_overrun{ + "Could not convert string to string: too long for buffer."}; + // Include the trailing zero. + value.copy(begin, std::size(value)); + begin[std::size(value)] = '\0'; + return begin + std::size(value) + 1; + } + + static zview to_buf(char *begin, char *end, std::string const &value) + { + return generic_to_buf(begin, end, value); + } + + static std::size_t size_buffer(std::string const &value) noexcept + { + return std::size(value) + 1; + } +}; + + +/// There's no real null for `std::string_view`. +/** I'm not sure how clear-cut this is: a `string_view` may have a null + * data pointer, which is analogous to a null `char` pointer. + */ +template<> struct nullness : no_null +{}; + + +/// String traits for `string_view`. +template<> struct string_traits +{ + static constexpr std::size_t + size_buffer(std::string_view const &value) noexcept + { + return std::size(value) + 1; + } + + static char *into_buf(char *begin, char *end, std::string_view const &value) + { + if (internal::cmp_greater_equal(std::size(value), end - begin)) + throw conversion_overrun{ + "Could not store string_view: too long for buffer."}; + value.copy(begin, std::size(value)); + begin[std::size(value)] = '\0'; + return begin + std::size(value) + 1; + } + + /// Don't convert to this type; it has nowhere to store its contents. + static std::string_view from_string(std::string_view) = delete; +}; + + +template<> struct nullness : no_null +{}; + + +/// String traits for `zview`. +template<> struct string_traits +{ + static constexpr std::size_t + size_buffer(std::string_view const &value) noexcept + { + return std::size(value) + 1; + } + + static char *into_buf(char *begin, char *end, zview const &value) + { + auto const size{std::size(value)}; + if (internal::cmp_less_equal(end - begin, std::size(value))) + throw conversion_overrun{"Not enough buffer space to store this zview."}; + value.copy(begin, size); + begin[size] = '\0'; + return begin + size + 1; + } + + static std::string_view to_buf(char *begin, char *end, zview const &value) + { + return {into_buf(begin, end, value), std::size(value)}; + } + + /// Don't convert to this type; it has nowhere to store its contents. + static zview from_string(std::string_view) = delete; +}; + + +template<> struct nullness : no_null +{}; + + +template<> struct string_traits +{ + static std::size_t size_buffer(std::stringstream const &) = delete; + + static std::stringstream from_string(std::string_view text) + { + std::stringstream stream; + stream.write(text.data(), std::streamsize(std::size(text))); + return stream; + } + + static char *into_buf(char *, char *, std::stringstream const &) = delete; + static std::string_view + to_buf(char *, char *, std::stringstream const &) = delete; +}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = true; + static constexpr bool is_null(std::nullptr_t const &) noexcept + { + return true; + } + static constexpr std::nullptr_t null() noexcept { return nullptr; } +}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = true; + static constexpr bool is_null(std::nullopt_t const &) noexcept + { + return true; + } + static constexpr std::nullopt_t null() noexcept { return std::nullopt; } +}; + + +template<> struct nullness +{ + static constexpr bool has_null = true; + static constexpr bool always_null = true; + static constexpr bool is_null(std::monostate const &) noexcept + { + return true; + } + static constexpr std::monostate null() noexcept { return {}; } +}; + + +template struct nullness> +{ + static constexpr bool has_null = true; + static constexpr bool always_null = false; + static constexpr bool is_null(std::unique_ptr const &t) noexcept + { + return not t or pqxx::is_null(*t); + } + static constexpr std::unique_ptr null() { return {}; } +}; + + +template +struct string_traits> +{ + static std::unique_ptr from_string(std::string_view text) + { + return std::make_unique(string_traits::from_string(text)); + } + + static char * + into_buf(char *begin, char *end, std::unique_ptr const &value) + { + return string_traits::into_buf(begin, end, *value); + } + + static zview + to_buf(char *begin, char *end, std::unique_ptr const &value) + { + if (value) + return string_traits::to_buf(begin, end, *value); + else + return {}; + } + + static std::size_t + size_buffer(std::unique_ptr const &value) noexcept + { + return pqxx::size_buffer(*value.get()); + } +}; + + +template +inline format param_format(std::unique_ptr const &value) +{ + return param_format(*value); +} + + +template +inline constexpr bool is_unquoted_safe>{ + is_unquoted_safe}; + + +template struct nullness> +{ + static constexpr bool has_null = true; + static constexpr bool always_null = false; + static constexpr bool is_null(std::shared_ptr const &t) noexcept + { + return not t or pqxx::is_null(*t); + } + static constexpr std::shared_ptr null() { return {}; } +}; + + +template struct string_traits> +{ + static std::shared_ptr from_string(std::string_view text) + { + return std::make_shared(string_traits::from_string(text)); + } + + static zview to_buf(char *begin, char *end, std::shared_ptr const &value) + { + return string_traits::to_buf(begin, end, *value); + } + static char * + into_buf(char *begin, char *end, std::shared_ptr const &value) + { + return string_traits::into_buf(begin, end, *value); + } + static std::size_t size_buffer(std::shared_ptr const &value) noexcept + { + return pqxx::size_buffer(*value); + } +}; + + +template format param_format(std::shared_ptr const &value) +{ + return param_format(*value); +} + + +template +inline constexpr bool is_unquoted_safe>{ + is_unquoted_safe}; + + +template<> +struct nullness> + : no_null> +{}; + + +#if defined(PQXX_HAVE_CONCEPTS) +template struct nullness : no_null +{}; + + +template inline constexpr format param_format(DATA const &) +{ + return format::binary; +} + + +template struct string_traits +{ + static std::size_t size_buffer(DATA const &value) noexcept + { + return internal::size_esc_bin(std::size(value)); + } + + static zview to_buf(char *begin, char *end, DATA const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf(char *begin, char *end, DATA const &value) + { + auto const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to escape binary data."}; + internal::esc_bin(value, begin); + return begin + budget; + } + + static DATA from_string(std::string_view text) + { + auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; + std::basic_string buf; + buf.resize(size); + pqxx::internal::unesc_bin(text, reinterpret_cast(buf.data())); + return buf; + } +}; +#endif // PQXX_HAVE_CONCEPTS + + +template<> struct string_traits> +{ + static std::size_t + size_buffer(std::basic_string const &value) noexcept + { + return internal::size_esc_bin(std::size(value)); + } + + static zview + to_buf(char *begin, char *end, std::basic_string const &value) + { + return generic_to_buf(begin, end, value); + } + + static char * + into_buf(char *begin, char *end, std::basic_string const &value) + { + auto const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to escape binary data."}; + internal::esc_bin(value, begin); + return begin + budget; + } + + static std::basic_string from_string(std::string_view text) + { + auto const size{pqxx::internal::size_unesc_bin(std::size(text))}; + std::basic_string buf; + buf.resize(size); + pqxx::internal::unesc_bin(text, reinterpret_cast(buf.data())); + return buf; + } +}; + + +template<> +inline constexpr format param_format(std::basic_string const &) +{ + return format::binary; +} + + +template<> +struct nullness> + : no_null> +{}; + + +template<> struct string_traits> +{ + static std::size_t + size_buffer(std::basic_string_view const &value) noexcept + { + return internal::size_esc_bin(std::size(value)); + } + + static zview to_buf( + char *begin, char *end, std::basic_string_view const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf( + char *begin, char *end, std::basic_string_view const &value) + { + auto const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to escape binary data."}; + internal::esc_bin(value, begin); + return begin + budget; + } + + // There's no from_string, because there's nobody to hold the data. +}; + +template<> +inline constexpr format param_format(std::basic_string_view const &) +{ + return format::binary; +} +} // namespace pqxx + + +namespace pqxx::internal +{ +/// String traits for SQL arrays. +template struct array_string_traits +{ +private: + using elt_type = strip_t>; + using elt_traits = string_traits; + static constexpr zview s_null{"NULL"}; + +public: + static zview to_buf(char *begin, char *end, Container const &value) + { + return generic_to_buf(begin, end, value); + } + + static char *into_buf(char *begin, char *end, Container const &value) + { + std::size_t const budget{size_buffer(value)}; + if (internal::cmp_less(end - begin, budget)) + throw conversion_overrun{ + "Not enough buffer space to convert array to string."}; + + char *here = begin; + *here++ = '{'; + + bool nonempty{false}; + for (auto const &elt : value) + { + if (is_null(elt)) + { + s_null.copy(here, std::size(s_null)); + here += std::size(s_null); + } + else if constexpr (is_sql_array) + { + // Render nested array in-place. Then erase the trailing zero. + here = elt_traits::into_buf(here, end, elt) - 1; + } + else if constexpr (is_unquoted_safe) + { + // No need to quote or escape. Just convert the value straight into + // its place in the array, and "backspace" the trailing zero. + here = elt_traits::into_buf(here, end, elt) - 1; + } + else + { + *here++ = '"'; + + // Use the tail end of the destination buffer as an intermediate + // buffer. + auto const elt_budget{pqxx::size_buffer(elt)}; + for (char const c : elt_traits::to_buf(end - elt_budget, end, elt)) + { + if (c == '\\' or c == '"') + *here++ = '\\'; + *here++ = c; + } + *here++ = '"'; + } + *here++ = array_separator; + nonempty = true; + } + + // Erase that last comma, if present. + if (nonempty) + here--; + + *here++ = '}'; + *here++ = '\0'; + + return here; + } + + static std::size_t size_buffer(Container const &value) noexcept + { + if constexpr (is_unquoted_safe) + return 3 + std::accumulate( + std::begin(value), std::end(value), std::size_t{}, + [](std::size_t acc, elt_type const &elt) { + return acc + + (pqxx::is_null(elt) ? + std::size(s_null) : + elt_traits::size_buffer(elt)) - + 1; + }); + else + return 3 + std::accumulate( + std::begin(value), std::end(value), std::size_t{}, + [](std::size_t acc, elt_type const &elt) { + // Opening and closing quotes, plus worst-case escaping, + // but don't count the trailing zeroes. + std::size_t const elt_size{ + pqxx::is_null(elt) ? std::size(s_null) : + elt_traits::size_buffer(elt) - 1}; + return acc + 2 * elt_size + 2; + }); + } + + // We don't yet support parsing of array types using from_string. Doing so + // would require a reference to the connection. +}; +} // namespace pqxx::internal + + +namespace pqxx +{ +template +struct nullness> : no_null> +{}; + + +template +struct string_traits> + : internal::array_string_traits> +{}; + + +/// We don't know how to pass array params in binary format, so pass as text. +template +inline constexpr format param_format(std::vector const &) +{ + return format::text; +} + + +/// A `std::vector` is a binary string. Other vectors are not. +template +inline constexpr format param_format(std::vector const &) +{ + return format::binary; +} + + +template inline constexpr bool is_sql_array>{true}; + + +template +struct nullness> : no_null> +{}; + + +template +struct string_traits> + : internal::array_string_traits> +{}; + + +/// We don't know how to pass array params in binary format, so pass as text. +template +inline constexpr format param_format(std::array const &) +{ + return format::text; +} + + +/// An array of `std::byte` is a binary string. +template +inline constexpr format param_format(std::array const &) +{ + return format::binary; +} + + +template +inline constexpr bool is_sql_array>{true}; +} // namespace pqxx + + +namespace pqxx +{ +template inline std::string to_string(T const &value) +{ + if (is_null(value)) + throw conversion_error{ + "Attempt to convert null " + type_name + " to a string."}; + + std::string buf; + // We can't just reserve() space; modifying the terminating zero leads to + // undefined behaviour. + buf.resize(size_buffer(value)); + auto const data{buf.data()}; + auto const end{ + string_traits::into_buf(data, data + std::size(buf), value)}; + buf.resize(static_cast(end - data - 1)); + return buf; +} + + +template<> inline std::string to_string(float const &value) +{ + return internal::to_string_float(value); +} +template<> inline std::string to_string(double const &value) +{ + return internal::to_string_float(value); +} +template<> inline std::string to_string(long double const &value) +{ + return internal::to_string_float(value); +} +template<> inline std::string to_string(std::stringstream const &value) +{ + return value.str(); +} + + +template inline void into_string(T const &value, std::string &out) +{ + if (is_null(value)) + throw conversion_error{ + "Attempt to convert null " + type_name + " to a string."}; + + // We can't just reserve() data; modifying the terminating zero leads to + // undefined behaviour. + out.resize(size_buffer(value) + 1); + auto const data{out.data()}; + auto const end{ + string_traits::into_buf(data, data + std::size(out), value)}; + out.resize(static_cast(end - data - 1)); +} +} // namespace pqxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encoding_group.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encoding_group.hxx new file mode 100644 index 000000000..e17736e5b --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encoding_group.hxx @@ -0,0 +1,60 @@ +/** Enum type for supporting encodings in libpqxx + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ENCODING_GROUP +#define PQXX_H_ENCODING_GROUP + +#include + +namespace pqxx::internal +{ +// Types of encodings supported by PostgreSQL, see +// https://www.postgresql.org/docs/current/static/multibyte.html#CHARSET-TABLE +enum class encoding_group +{ + // Handles all single-byte fixed-width encodings + MONOBYTE, + + // Multibyte encodings. + // Many of these can embed ASCII-like bytes inside multibyte characters, + // notably Big5, SJIS, SHIFT_JIS_2004, GP18030, GBK, JOHAB, UHC. + BIG5, + EUC_CN, + // TODO: Merge EUC_JP and EUC_JIS_2004? + EUC_JP, + EUC_JIS_2004, + EUC_KR, + EUC_TW, + GB18030, + GBK, + JOHAB, + MULE_INTERNAL, + // TODO: Merge SJIS and SHIFT_JIS_2004? + SJIS, + SHIFT_JIS_2004, + UHC, + UTF8, +}; + + +// TODO:: Can we just use string_view now? +/// Function type: "find the end of the current glyph." +/** This type of function takes a text buffer, and a location in that buffer, + * and returns the location one byte past the end of the current glyph. + * + * The start offset marks the beginning of the current glyph. It must fall + * within the buffer. + * + * There are multiple different glyph scanner implementations, for different + * kinds of encodings. + */ +using glyph_scanner_func = + std::size_t(char const buffer[], std::size_t buffer_len, std::size_t start); +} // namespace pqxx::internal + +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encodings.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encodings.hxx new file mode 100644 index 000000000..ba7fecc70 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/encodings.hxx @@ -0,0 +1,90 @@ +/** Internal string encodings support for libpqxx + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ENCODINGS +#define PQXX_H_ENCODINGS + +#include "pqxx/internal/encoding_group.hxx" + +#include +#include + + +namespace pqxx::internal +{ +char const *name_encoding(int encoding_id); + +/// Convert libpq encoding enum or encoding name to its libpqxx group. +encoding_group enc_group(int /* libpq encoding ID */); +encoding_group enc_group(std::string_view); + + +/// Look up the glyph scanner function for a given encoding group. +/** To identify the glyph boundaries in a buffer, call this to obtain the + * scanner function appropriate for the buffer's encoding. Then, repeatedly + * call the scanner function to find the glyphs. + */ +PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group); + + +// TODO: For ASCII search, treat UTF8/EUC_*/MULE_INTERNAL as MONOBYTE. + +/// Find any of the ASCII characters `NEEDLE` in `haystack`. +/** Scans through `haystack` until it finds a single-byte character that + * matches any value in `NEEDLE`. + * + * If it finds one, returns its offset. If not, returns the end of the + * haystack. + */ +template +inline std::size_t find_char( + glyph_scanner_func *scanner, std::string_view haystack, + std::size_t here = 0u) +{ + auto const sz{std::size(haystack)}; + auto const data{std::data(haystack)}; + while (here < sz) + { + auto next{scanner(data, sz, here)}; + // (For some reason gcc had a problem with a right-fold here. But clang + // was fine.) + if ((... or (data[here] == NEEDLE))) + { + // Also check against a multibyte character starting with a bytes which + // just happens to match one of the ASCII bytes we're looking for. It'd + // be cleaner to check that first, but either works. So, let's apply the + // most selective filter first and skip this check in almost all cases. + if (next == here + 1) + return here; + } + + // Nope, no hit. Move on. + here = next; + } + return sz; +} + + +/// Iterate over the glyphs in a buffer. +/** Scans the glyphs in the buffer, and for each, passes its begin and its + * one-past-end pointers to `callback`. + */ +template +inline void for_glyphs( + encoding_group enc, CALLABLE callback, char const buffer[], + std::size_t buffer_len, std::size_t start = 0) +{ + auto const scan{get_glyph_scanner(enc)}; + for (std::size_t here = start, next; here < buffer_len; here = next) + { + next = scan(buffer, buffer_len, here); + callback(buffer + here, buffer + next); + } +} +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-errorhandler.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-errorhandler.hxx new file mode 100644 index 000000000..ffc12a6cf --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-errorhandler.hxx @@ -0,0 +1,26 @@ +#include + +namespace pqxx +{ +class connection; +class errorhandler; +} // namespace pqxx + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_errorhandler : callgate +{ + friend class pqxx::errorhandler; + + connection_errorhandler(reference x) : super(x) {} + + void register_errorhandler(errorhandler *h) + { + home().register_errorhandler(h); + } + void unregister_errorhandler(errorhandler *h) + { + home().unregister_errorhandler(h); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-largeobject.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-largeobject.hxx new file mode 100644 index 000000000..49feaf9e6 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-largeobject.hxx @@ -0,0 +1,35 @@ +#include + +#include +#include + +namespace pqxx +{ +class blob; +class largeobject; +} // namespace pqxx + + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_largeobject : callgate +{ + friend class pqxx::blob; + friend class pqxx::largeobject; + + connection_largeobject(reference x) : super(x) {} + + pq::PGconn *raw_connection() const { return home().raw_connection(); } +}; + + +class PQXX_PRIVATE const_connection_largeobject : callgate +{ + friend class pqxx::blob; + friend class pqxx::largeobject; + + const_connection_largeobject(reference x) : super(x) {} + + std::string error_message() const { return home().err_msg(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-notification_receiver.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-notification_receiver.hxx new file mode 100644 index 000000000..0bcb2db17 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-notification_receiver.hxx @@ -0,0 +1,29 @@ +#include + +#include "pqxx/connection.hxx" + + +namespace pqxx +{ +class notification_receiver; +} + + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_notification_receiver : callgate +{ + friend class pqxx::notification_receiver; + + connection_notification_receiver(reference x) : super(x) {} + + void add_receiver(notification_receiver *receiver) + { + home().add_receiver(receiver); + } + void remove_receiver(notification_receiver *receiver) noexcept + { + home().remove_receiver(receiver); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-pipeline.hxx new file mode 100644 index 000000000..c6ae6e17a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-pipeline.hxx @@ -0,0 +1,23 @@ +#include "pqxx/internal/libpq-forward.hxx" +#include + +#include "pqxx/pipeline.hxx" + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_pipeline : callgate +{ + friend class pqxx::pipeline; + + connection_pipeline(reference x) : super(x) {} + + void start_exec(char const query[]) { home().start_exec(query); } + pqxx::internal::pq::PGresult *get_result() { return home().get_result(); } + void cancel_query() { home().cancel_query(); } + + bool consume_input() noexcept { return home().consume_input(); } + bool is_busy() const noexcept { return home().is_busy(); } + + int encoding_id() { return home().encoding_id(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-sql_cursor.hxx new file mode 100644 index 000000000..51a889844 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-sql_cursor.hxx @@ -0,0 +1,19 @@ +#include + +namespace pqxx::internal +{ +class sql_cursor; +} + + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_sql_cursor : callgate +{ + friend class pqxx::internal::sql_cursor; + + connection_sql_cursor(reference x) : super(x) {} + + result exec(char const query[]) { return home().exec(query); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_from.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_from.hxx new file mode 100644 index 000000000..8961e7146 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_from.hxx @@ -0,0 +1,15 @@ +#include + +#include "pqxx/connection.hxx" + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_stream_from : callgate +{ + friend class pqxx::stream_from; + + connection_stream_from(reference x) : super{x} {} + + auto read_copy_line() { return home().read_copy_line(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_to.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_to.hxx new file mode 100644 index 000000000..a6974fb21 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-stream_to.hxx @@ -0,0 +1,17 @@ +#include + +#include "pqxx/stream_to.hxx" + + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_stream_to : callgate +{ + friend class pqxx::stream_to; + + connection_stream_to(reference x) : super(x) {} + + void write_copy_line(std::string_view line) { home().write_copy_line(line); } + void end_copy_write() { home().end_copy_write(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-transaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-transaction.hxx new file mode 100644 index 000000000..74d659253 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/connection-transaction.hxx @@ -0,0 +1,44 @@ +#include + +namespace pqxx +{ +class connection; +} + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE connection_transaction : callgate +{ + friend class pqxx::transaction_base; + + connection_transaction(reference x) : super(x) {} + + template result exec(STRING query, std::string_view desc) + { + return home().exec(query, desc); + } + + void register_transaction(transaction_base *t) + { + home().register_transaction(t); + } + void unregister_transaction(transaction_base *t) noexcept + { + home().unregister_transaction(t); + } + + auto read_copy_line() { return home().read_copy_line(); } + void write_copy_line(std::string_view line) { home().write_copy_line(line); } + void end_copy_write() { home().end_copy_write(); } + + result exec_prepared(zview statement, internal::c_params const &args) + { + return home().exec_prepared(statement, args); + } + + result exec_params(zview query, internal::c_params const &args) + { + return home().exec_params(query, args); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/errorhandler-connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/errorhandler-connection.hxx new file mode 100644 index 000000000..5560cedec --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/errorhandler-connection.hxx @@ -0,0 +1,13 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE errorhandler_connection : callgate +{ + friend class pqxx::connection; + + errorhandler_connection(reference x) : super(x) {} + + void unregister() noexcept { home().unregister(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx new file mode 100644 index 000000000..296d22145 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursor_iterator-icursorstream.hxx @@ -0,0 +1,24 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE icursor_iterator_icursorstream : callgate +{ + friend class pqxx::icursorstream; + + icursor_iterator_icursorstream(reference x) : super(x) {} + + icursor_iterator::difference_type pos() const noexcept + { + return home().pos(); + } + + icursor_iterator *get_prev() { return home().m_prev; } + void set_prev(icursor_iterator *i) { home().m_prev = i; } + + icursor_iterator *get_next() { return home().m_next; } + void set_next(icursor_iterator *i) { home().m_next = i; } + + void fill(result const &r) { home().fill(r); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx new file mode 100644 index 000000000..56056d5ef --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/icursorstream-icursor_iterator.hxx @@ -0,0 +1,32 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE icursorstream_icursor_iterator : callgate +{ + friend class pqxx::icursor_iterator; + + icursorstream_icursor_iterator(reference x) : super(x) {} + + void insert_iterator(icursor_iterator *i) noexcept + { + home().insert_iterator(i); + } + + void remove_iterator(icursor_iterator *i) const noexcept + { + home().remove_iterator(i); + } + + icursorstream::size_type forward() { return home().forward(); } + icursorstream::size_type forward(icursorstream::size_type n) + { + return home().forward(n); + } + + void service_iterators(icursorstream::difference_type p) + { + home().service_iterators(p); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-connection.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-connection.hxx new file mode 100644 index 000000000..daa0808c0 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-connection.hxx @@ -0,0 +1,14 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE result_connection : callgate +{ + friend class pqxx::connection; + + result_connection(reference x) : super(x) {} + + operator bool() const { return bool(home()); } + bool operator!() const { return not home(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-creation.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-creation.hxx new file mode 100644 index 000000000..3d9205f2c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-creation.hxx @@ -0,0 +1,24 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE result_creation : callgate +{ + friend class pqxx::connection; + friend class pqxx::pipeline; + + result_creation(reference x) : super(x) {} + + static result create( + internal::pq::PGresult *rhs, std::shared_ptr const &query, + encoding_group enc) + { + return result(rhs, query, enc); + } + + void check_status(std::string_view desc = ""sv) const + { + return home().check_status(desc); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-pipeline.hxx new file mode 100644 index 000000000..3ebe436d2 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-pipeline.hxx @@ -0,0 +1,16 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE result_pipeline : callgate +{ + friend class pqxx::pipeline; + + result_pipeline(reference x) : super(x) {} + + std::shared_ptr query_ptr() const + { + return home().query_ptr(); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-sql_cursor.hxx new file mode 100644 index 000000000..78b450739 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/result-sql_cursor.hxx @@ -0,0 +1,13 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE result_sql_cursor : callgate +{ + friend class pqxx::internal::sql_cursor; + + result_sql_cursor(reference x) : super(x) {} + + char const *cmd_status() const noexcept { return home().cmd_status(); } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-sql_cursor.hxx new file mode 100644 index 000000000..4ed78dc93 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-sql_cursor.hxx @@ -0,0 +1,10 @@ +#include + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE transaction_sql_cursor : callgate +{ + friend class pqxx::internal::sql_cursor; + transaction_sql_cursor(reference x) : super(x) {} +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-transaction_focus.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-transaction_focus.hxx new file mode 100644 index 000000000..ca7939a99 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/gates/transaction-transaction_focus.hxx @@ -0,0 +1,30 @@ +#include + +#include "pqxx/transaction_base.hxx" + +namespace pqxx::internal::gate +{ +class PQXX_PRIVATE transaction_transaction_focus : callgate +{ + friend class pqxx::transaction_focus; + + transaction_transaction_focus(reference x) : super(x) {} + + void register_focus(transaction_focus *focus) + { + home().register_focus(focus); + } + void unregister_focus(transaction_focus *focus) noexcept + { + home().unregister_focus(focus); + } + void register_pending_error(zview error) + { + home().register_pending_error(error); + } + void register_pending_error(std::string &&error) + { + home().register_pending_error(std::move(error)); + } +}; +} // namespace pqxx::internal::gate diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-post.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-post.hxx new file mode 100644 index 000000000..ff6bf8986 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-post.hxx @@ -0,0 +1,22 @@ +/* Compiler deficiency workarounds for compiling libpqxx headers. + * + * To be included at the end of each libpqxx header, in order to restore the + * client program's settings. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +// NO GUARDS HERE! This code should be executed every time! + +#if defined(_MSC_VER) +# pragma warning(pop) // Restore compiler's warning state +#endif + +#if !defined(PQXX_HEADER_PRE) +# error "Include pqxx/internal/header-post.hxx AFTER its 'pre' counterpart." +#endif + +#undef PQXX_HEADER_PRE diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-pre.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-pre.hxx new file mode 100644 index 000000000..abc1a398d --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/header-pre.hxx @@ -0,0 +1,169 @@ +/* Compiler settings for compiling libpqxx headers, and workarounds for all. + * + * Include this before including any other libpqxx headers from within libpqxx. + * And to balance it out, also include header-post.hxx at the end of the batch + * of headers. + * + * The public libpqxx headers (e.g. ``) include this already; + * there's no need to do this from within an application. + * + * Include this file at the highest aggregation level possible to avoid nesting + * and to keep things simple. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ + +// NO GUARD HERE! This part should be included every time this file is. +#if defined(_MSC_VER) + +// Save compiler's warning state, and set warning level 4 for maximum +// sensitivity to warnings. +# pragma warning(push, 4) + +// Visual C++ generates some entirely unreasonable warnings. Disable them. +// Copy constructor could not be generated. +# pragma warning(disable : 4511) +// Assignment operator could not be generated. +# pragma warning(disable : 4512) +// Can't expose outside classes without exporting them. Except the MSVC docs +// say please ignore the warning if it's a standard library class. +# pragma warning(disable : 4251) +// Can't derive library classes from outside classes without exporting them. +// Except the MSVC docs say please ignore the warning if the parent class is +// in the standard library. +# pragma warning(disable : 4275) +// Can't inherit from non-exported class. +# pragma warning(disable : 4275) + +#endif // _MSC_VER + + +#if defined(PQXX_HEADER_PRE) +# error "Avoid nesting #include of pqxx/internal/header-pre.hxx." +#endif + +#define PQXX_HEADER_PRE + + +// Workarounds & definitions that need to be included even in library's headers +#include "pqxx/config-public-compiler.h" + +// Enable ISO-646 alternative operaotr representations: "and" instead of "&&" +// etc. on older compilers. C++20 removes this header. +#if __has_include() +# include +#endif + + +#if defined(PQXX_HAVE_GCC_PURE) +/// Declare function "pure": no side effects, only reads globals and its args. +# define PQXX_PURE __attribute__((pure)) +#else +# define PQXX_PURE /* pure */ +#endif + + +#if defined(__GNUC__) +/// Tell the compiler to optimise a function for size, not speed. +# define PQXX_COLD __attribute__((cold)) +#else +# define PQXX_COLD /* cold */ +#endif + + +// Workarounds for Windows +#ifdef _WIN32 + +/* For now, export DLL symbols if _DLL is defined. This is done automatically + * by the compiler when linking to the dynamic version of the runtime library, + * according to "gzh" + */ +# if defined(PQXX_SHARED) && !defined(PQXX_LIBEXPORT) +# define PQXX_LIBEXPORT __declspec(dllimport) +# endif // PQXX_SHARED && !PQXX_LIBEXPORT + + +// Workarounds for Microsoft Visual C++ +# ifdef _MSC_VER + +// Suppress vtables on abstract classes. +# define PQXX_NOVTABLE __declspec(novtable) + +// Automatically link with the appropriate libpq (static or dynamic, debug or +// release). The default is to use the release DLL. Define PQXX_PQ_STATIC to +// link to a static version of libpq, and _DEBUG to link to a debug version. +// The two may be combined. +# if defined(PQXX_AUTOLINK) +# if defined(PQXX_PQ_STATIC) +# ifdef _DEBUG +# pragma comment(lib, "libpqd") +# else +# pragma comment(lib, "libpq") +# endif +# else +# ifdef _DEBUG +# pragma comment(lib, "libpqddll") +# else +# pragma comment(lib, "libpqdll") +# endif +# endif +# endif + +// If we're not compiling libpqxx itself, automatically link with the +// appropriate libpqxx library. To link with the libpqxx DLL, define +// PQXX_SHARED; the default is to link with the static library. A static link +// is the recommended practice. +// +// The preprocessor macro PQXX_INTERNAL is used to detect whether we +// are compiling the libpqxx library itself. When you compile the library +// yourself using your own project file, make sure to include this macro. +# if defined(PQXX_AUTOLINK) && !defined(PQXX_INTERNAL) +# ifdef PQXX_SHARED +# ifdef _DEBUG +# pragma comment(lib, "libpqxxD") +# else +# pragma comment(lib, "libpqxx") +# endif +# else // !PQXX_SHARED +# ifdef _DEBUG +# pragma comment(lib, "libpqxx_staticD") +# else +# pragma comment(lib, "libpqxx_static") +# endif +# endif +# endif + +# endif // _MSC_VER + +#elif defined(PQXX_HAVE_GCC_VISIBILITY) // !_WIN32 + +# define PQXX_LIBEXPORT __attribute__((visibility("default"))) +# define PQXX_PRIVATE __attribute__((visibility("hidden"))) + +#endif // PQXX_HAVE_GCC_VISIBILITY + + +#ifndef PQXX_LIBEXPORT +# define PQXX_LIBEXPORT /* libexport */ +#endif + +#ifndef PQXX_PRIVATE +# define PQXX_PRIVATE /* private */ +#endif + +#ifndef PQXX_NOVTABLE +# define PQXX_NOVTABLE /* novtable */ +#endif + +// C++20: Assume support. +#if defined(PQXX_HAVE_LIKELY) +# define PQXX_LIKELY [[likely]] +# define PQXX_UNLIKELY [[unlikely]] +#else +# define PQXX_LIKELY /* [[likely]] */ +# define PQXX_UNLIKELY /* [[unlikely]] */ +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-post.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-post.hxx new file mode 100644 index 000000000..cebcf0594 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-post.hxx @@ -0,0 +1,15 @@ +/// End a code block started by "ignore-deprecated-pre.hxx". + +#if !defined(PQXX_IGNORING_DEPRECATED) +# error "Ended an 'ignore-deprecated' block while none was active." +#endif + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif // __GNUC__ + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#undef PQXX_IGNORING_DEPRECATED diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-pre.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-pre.hxx new file mode 100644 index 000000000..8ac57afaa --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/ignore-deprecated-pre.hxx @@ -0,0 +1,28 @@ +/** Start a block of deprecated code which may call other deprecated code. + * + * Most compilers will emit warnings when deprecated code is invoked from + * non-deprecated code. But some compilers (notably gcc) will always emit the + * warning even when the calling code is also deprecated. + * + * This header starts a block where those warnings are suppressed. It can be + * included inside a code block. + * + * Always match the #include with a closing #include of + * "ignore-deprecated-post.hxx". To avoid mistakes, keep the enclosed area as + * small as possible. + */ +#if defined(PQXX_IGNORING_DEPRECATED) +# error "Started an 'ignore-deprecated' block inside another." +#endif + +#define PQXX_IGNORING_DEPRECATED + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif // __GNUC__ + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4996) +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/libpq-forward.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/libpq-forward.hxx new file mode 100644 index 000000000..9e74f79ec --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/libpq-forward.hxx @@ -0,0 +1,31 @@ +/** Minimal forward declarations of libpq types needed in libpqxx headers. + * + * DO NOT INCLUDE THIS FILE when building client programs. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +extern "C" +{ + struct pg_conn; + struct pg_result; + struct pgNotify; +} + +/// Forward declarations of libpq types as needed in libpqxx headers. +namespace pqxx::internal::pq +{ +using PGconn = pg_conn; +using PGresult = pg_result; +using PGnotify = pgNotify; +using PQnoticeProcessor = void (*)(void *, char const *); +} // namespace pqxx::internal::pq + +namespace pqxx +{ +/// PostgreSQL database row identifier. +using oid = unsigned int; +} // namespace pqxx diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iter.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iter.hxx new file mode 100644 index 000000000..1fa1f7d8a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iter.hxx @@ -0,0 +1,124 @@ +/** Result loops. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_RESULT_ITER +#define PQXX_H_RESULT_ITER + +#include + +#include "pqxx/strconv.hxx" + +namespace pqxx +{ +class result; +} // namespace pqxx + + +namespace pqxx::internal +{ +// C++20: Replace with generator? +/// Iterator for looped unpacking of a result. +template class result_iter +{ +public: + using value_type = std::tuple; + + /// Construct an "end" iterator. + result_iter() = default; + + explicit result_iter(result const &home) : + m_home{&home}, m_size{std::size(home)} + { + if (not std::empty(home)) + read(); + } + result_iter(result_iter const &) = default; + + result_iter &operator++() + { + m_index++; + if (m_index >= m_size) + m_home = nullptr; + else + read(); + return *this; + } + + /// Comparison only works for comparing to end(). + bool operator==(result_iter const &rhs) const + { + return m_home == rhs.m_home; + } + bool operator!=(result_iter const &rhs) const { return not(*this == rhs); } + + value_type const &operator*() const { return m_value; } + +private: + void read() { (*m_home)[m_index].convert(m_value); } + + result const *m_home{nullptr}; + result::size_type m_index{0}; + result::size_type m_size; + value_type m_value; +}; + + +template class result_iteration +{ +public: + using iterator = result_iter; + explicit result_iteration(result const &home) : m_home{home} + { + constexpr auto tup_size{sizeof...(TYPE)}; + if (home.columns() != tup_size) + throw usage_error{internal::concat( + "Tried to extract ", to_string(tup_size), + " field(s) from a result with ", to_string(home.columns()), + " column(s).")}; + } + iterator begin() const + { + if (std::size(m_home) == 0) + return end(); + else + return iterator{m_home}; + } + iterator end() const { return {}; } + +private: + pqxx::result const &m_home; +}; +} // namespace pqxx::internal + + +template inline auto pqxx::result::iter() const +{ + return pqxx::internal::result_iteration{*this}; +} + + +template +inline void pqxx::result::for_each(CALLABLE &&func) const +{ + using args_tuple = internal::args_t; + constexpr auto sz{std::tuple_size_v}; + static_assert( + sz > 0, + "Callback for for_each must take parameters, one for each column in the " + "result."); + + auto const cols{this->columns()}; + if (sz != cols) + throw usage_error{internal::concat( + "Callback to for_each takes ", sz, "parameter", (sz == 1) ? "" : "s", + ", but result set has ", cols, "field", (cols == 1) ? "" : "s", ".")}; + + using pass_tuple = pqxx::internal::strip_types_t; + for (auto const r : *this) std::apply(func, r.as_tuple()); +} +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iterator.hxx new file mode 100644 index 000000000..3f27a1d3f --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/result_iterator.hxx @@ -0,0 +1,389 @@ +/* Definitions for the pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_RESULT_ITERATOR +#define PQXX_H_RESULT_ITERATOR + +#include "pqxx/row.hxx" + + +/* Result iterator. + * + * Don't include this header from your own application; it is included for you + * by other libpqxx headers. + */ + +namespace pqxx +{ +/// Iterator for rows in a result. Use as result::const_iterator. +/** A result, once obtained, cannot be modified. Therefore there is no + * plain iterator type for result. However its const_iterator type can be + * used to inspect its rows without changing them. + */ +class PQXX_LIBEXPORT const_result_iterator : public row +{ +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = row const; + using pointer = row const *; + using reference = row; + using size_type = result_size_type; + using difference_type = result_difference_type; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// Create an iterator, but in an unusable state. + const_result_iterator() noexcept = default; + /// Copy an iterator. + const_result_iterator(const_result_iterator const &) noexcept = default; + /// Move an iterator. + const_result_iterator(const_result_iterator &&) noexcept = default; + + /// Begin iterating a @ref row. + const_result_iterator(row const &t) noexcept : row{t} {} +#include "pqxx/internal/ignore-deprecated-post.hxx" + + /** + * @name Dereferencing operators + * + * An iterator "points to" its own row, which is also itself. This makes it + * easy to address a @ref result as a two-dimensional container, without + * going through the intermediate step of dereferencing the iterator. It + * makes the interface similar to C pointer/array semantics. + * + * IIRC Alex Stepanov, the inventor of the STL, once remarked that having + * this as standard behaviour for pointers would be useful in some + * algorithms. So even if this makes me look foolish, I would seem to be in + * distinguished company. + */ + //@{ + /// Dereference the iterator. + [[nodiscard]] pointer operator->() const { return this; } + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// Dereference the iterator. + [[nodiscard]] reference operator*() const { return *this; } +#include "pqxx/internal/ignore-deprecated-post.hxx" + //@} + + /** + * @name Field access + */ + //@{ + using row::back; + using row::front; + using row::operator[]; + using row::at; + using row::rownumber; + //@} + + /** + * @name Manipulations + */ + //@{ + const_result_iterator &operator=(const_result_iterator const &rhs) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + row::operator=(rhs); +#include "pqxx/internal/ignore-deprecated-post.hxx" + return *this; + } + + const_result_iterator &operator=(const_result_iterator &&rhs) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + row::operator=(std::move(rhs)); +#include "pqxx/internal/ignore-deprecated-post.hxx" + return *this; + } + + const_result_iterator operator++(int); + const_result_iterator &operator++() + { + ++m_index; + return *this; + } + const_result_iterator operator--(int); + const_result_iterator &operator--() + { + --m_index; + return *this; + } + + const_result_iterator &operator+=(difference_type i) + { + m_index += i; + return *this; + } + const_result_iterator &operator-=(difference_type i) + { + m_index -= i; + return *this; + } + + /// Interchange two iterators in an exception-safe manner. + void swap(const_result_iterator &other) noexcept + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + row::swap(other); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + //@} + + /** + * @name Comparisons + */ + //@{ + [[nodiscard]] bool operator==(const_result_iterator const &i) const + { + return m_index == i.m_index; + } + [[nodiscard]] bool operator!=(const_result_iterator const &i) const + { + return m_index != i.m_index; + } + [[nodiscard]] bool operator<(const_result_iterator const &i) const + { + return m_index < i.m_index; + } + [[nodiscard]] bool operator<=(const_result_iterator const &i) const + { + return m_index <= i.m_index; + } + [[nodiscard]] bool operator>(const_result_iterator const &i) const + { + return m_index > i.m_index; + } + [[nodiscard]] bool operator>=(const_result_iterator const &i) const + { + return m_index >= i.m_index; + } + //@} + + /** + * @name Arithmetic operators + */ + //@{ + [[nodiscard]] inline const_result_iterator operator+(difference_type) const; + friend const_result_iterator + operator+(difference_type, const_result_iterator const &); + [[nodiscard]] inline const_result_iterator operator-(difference_type) const; + [[nodiscard]] inline difference_type + operator-(const_result_iterator const &) const; + //@} + +private: + friend class pqxx::result; + const_result_iterator(pqxx::result const *r, result_size_type i) noexcept : + row{*r, i, r->columns()} + {} +}; + + +/// Reverse iterator for result. Use as result::const_reverse_iterator. +class PQXX_LIBEXPORT const_reverse_result_iterator + : private const_result_iterator +{ +public: + using super = const_result_iterator; + using iterator_type = const_result_iterator; + using iterator_type::difference_type; + using iterator_type::iterator_category; + using iterator_type::pointer; + using value_type = iterator_type::value_type; + using reference = iterator_type::reference; + + /// Create an iterator, but in an unusable state. + const_reverse_result_iterator() = default; + /// Copy an iterator. + const_reverse_result_iterator(const_reverse_result_iterator const &rhs) = + default; + /// Copy a reverse iterator from a regular iterator. + explicit const_reverse_result_iterator(const_result_iterator const &rhs) : + const_result_iterator{rhs} + { + super::operator--(); + } + + /// Move a regular iterator into a reverse iterator. + explicit const_reverse_result_iterator(const_result_iterator const &&rhs) : + const_result_iterator{std::move(rhs)} + { + super::operator--(); + } + + /// Return the underlying "regular" iterator (as per standard library). + [[nodiscard]] PQXX_PURE const_result_iterator base() const noexcept; + + /** + * @name Dereferencing operators + */ + //@{ + /// Dereference iterator. + using const_result_iterator::operator->; + /// Dereference iterator. + using const_result_iterator::operator*; + //@} + + /** + * @name Field access + */ + //@{ + using const_result_iterator::back; + using const_result_iterator::front; + using const_result_iterator::operator[]; + using const_result_iterator::at; + using const_result_iterator::rownumber; + //@} + + /** + * @name Manipulations + */ + //@{ + const_reverse_result_iterator & + operator=(const_reverse_result_iterator const &r) + { + iterator_type::operator=(r); + return *this; + } + const_reverse_result_iterator &operator=(const_reverse_result_iterator &&r) + { + iterator_type::operator=(std::move(r)); + return *this; + } + const_reverse_result_iterator &operator++() + { + iterator_type::operator--(); + return *this; + } + const_reverse_result_iterator operator++(int); + const_reverse_result_iterator &operator--() + { + iterator_type::operator++(); + return *this; + } + const_reverse_result_iterator operator--(int); + const_reverse_result_iterator &operator+=(difference_type i) + { + iterator_type::operator-=(i); + return *this; + } + const_reverse_result_iterator &operator-=(difference_type i) + { + iterator_type::operator+=(i); + return *this; + } + + void swap(const_reverse_result_iterator &other) noexcept + { + const_result_iterator::swap(other); + } + //@} + + /** + * @name Arithmetic operators + */ + //@{ + [[nodiscard]] const_reverse_result_iterator + operator+(difference_type i) const + { + return const_reverse_result_iterator(base() - i); + } + [[nodiscard]] const_reverse_result_iterator operator-(difference_type i) + { + return const_reverse_result_iterator(base() + i); + } + [[nodiscard]] difference_type + operator-(const_reverse_result_iterator const &rhs) const + { + return rhs.const_result_iterator::operator-(*this); + } + //@} + + /** + * @name Comparisons + */ + //@{ + [[nodiscard]] bool + operator==(const_reverse_result_iterator const &rhs) const noexcept + { + return iterator_type::operator==(rhs); + } + [[nodiscard]] bool + operator!=(const_reverse_result_iterator const &rhs) const noexcept + { + return not operator==(rhs); + } + + [[nodiscard]] bool operator<(const_reverse_result_iterator const &rhs) const + { + return iterator_type::operator>(rhs); + } + [[nodiscard]] bool operator<=(const_reverse_result_iterator const &rhs) const + { + return iterator_type::operator>=(rhs); + } + [[nodiscard]] bool operator>(const_reverse_result_iterator const &rhs) const + { + return iterator_type::operator<(rhs); + } + [[nodiscard]] bool operator>=(const_reverse_result_iterator const &rhs) const + { + return iterator_type::operator<=(rhs); + } + //@} +}; + + +inline const_result_iterator +const_result_iterator::operator+(result::difference_type o) const +{ + return {&m_result, size_type(result::difference_type(m_index) + o)}; +} + +inline const_result_iterator +operator+(result::difference_type o, const_result_iterator const &i) +{ + return i + o; +} + +inline const_result_iterator +const_result_iterator::operator-(result::difference_type o) const +{ + return {&m_result, result_size_type(result::difference_type(m_index) - o)}; +} + +inline result::difference_type +const_result_iterator::operator-(const const_result_iterator &i) const +{ + return result::difference_type(num() - i.num()); +} + +inline const_result_iterator result::end() const noexcept +{ + return {this, size()}; +} + + +inline const_result_iterator result::cend() const noexcept +{ + return end(); +} + + +inline const_reverse_result_iterator +operator+(result::difference_type n, const_reverse_result_iterator const &i) +{ + return const_reverse_result_iterator{i.base() - n}; +} + +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/sql_cursor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/sql_cursor.hxx new file mode 100644 index 000000000..a26d06306 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/sql_cursor.hxx @@ -0,0 +1,118 @@ +/** Internal wrapper for SQL cursors. Supports higher-level cursor classes. + * + * DO NOT INCLUDE THIS FILE DIRECTLY. Other headers include it for you. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_SQL_CURSOR +#define PQXX_H_SQL_CURSOR + +namespace pqxx::internal +{ +/// Cursor with SQL positioning semantics. +/** Thin wrapper around an SQL cursor, with SQL's ideas of positioning. + * + * SQL cursors have pre-increment/pre-decrement semantics, with on either end + * of the result set a special position that does not repesent a row. This + * class models SQL cursors for the purpose of implementing more C++-like + * semantics on top. + * + * Positions of actual rows are numbered starting at 1. Position 0 exists but + * does not refer to a row. There is a similar non-row position at the end of + * the result set. + * + * Don't use this at home. You deserve better. Use the stateles_cursor + * instead. + */ +class PQXX_LIBEXPORT sql_cursor : public cursor_base +{ +public: + sql_cursor( + transaction_base &t, std::string_view query, std::string_view cname, + cursor_base::access_policy ap, cursor_base::update_policy up, + cursor_base::ownership_policy op, bool hold); + + sql_cursor( + transaction_base &t, std::string_view cname, + cursor_base::ownership_policy op); + + ~sql_cursor() noexcept { close(); } + + result fetch(difference_type rows, difference_type &displacement); + result fetch(difference_type rows) + { + difference_type d = 0; + return fetch(rows, d); + } + difference_type move(difference_type rows, difference_type &displacement); + difference_type move(difference_type rows) + { + difference_type d = 0; + return move(rows, d); + } + + /// Current position, or -1 for unknown + /** + * The starting position, just before the first row, counts as position zero. + * + * Position may be unknown if (and only if) this cursor was adopted, and has + * never hit its starting position (position zero). + */ + difference_type pos() const noexcept { return m_pos; } + + /// End position, or -1 for unknown + /** + * Returns the final position, just after the last row in the result set. The + * starting position, just before the first row, counts as position zero. + * + * End position is unknown until it is encountered during use. + */ + difference_type endpos() const noexcept { return m_endpos; } + + /// Return zero-row result for this cursor. + result const &empty_result() const noexcept { return m_empty_result; } + + void close() noexcept; + +private: + difference_type adjust(difference_type hoped, difference_type actual); + static std::string stridestring(difference_type); + /// Initialize cached empty result. Call only at beginning or end! + void init_empty_result(transaction_base &); + + /// Connection in which this cursor lives. + connection &m_home; + + /// Zero-row result from this cursor (or plain empty one if cursor is + /// adopted) + result m_empty_result; + + result m_cached_current_row; + + /// Is this cursor adopted (as opposed to created by this cursor object)? + bool m_adopted; + + /// Will this cursor object destroy its SQL cursor when it dies? + cursor_base::ownership_policy m_ownership; + + /// At starting position (-1), somewhere in the middle (0), or past end (1) + int m_at_end; + + /// Position, or -1 for unknown + difference_type m_pos; + + /// End position, or -1 for unknown + difference_type m_endpos = -1; +}; + + +PQXX_LIBEXPORT result_size_type obtain_stateless_cursor_size(sql_cursor &); +PQXX_LIBEXPORT result stateless_cursor_retrieve( + sql_cursor &, result::difference_type size, + result::difference_type begin_pos, result::difference_type end_pos); +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/statement_parameters.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/statement_parameters.hxx new file mode 100644 index 000000000..b078bf6e0 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/statement_parameters.hxx @@ -0,0 +1,131 @@ +/** Common implementation for statement parameter lists. + * + * These are used for both prepared statements and parameterized statements. + * + * DO NOT INCLUDE THIS FILE DIRECTLY. Other headers include it for you. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STATEMENT_PARAMETER +#define PQXX_H_STATEMENT_PARAMETER + +#include +#include +#include +#include +#include + +#include "pqxx/binarystring.hxx" +#include "pqxx/strconv.hxx" +#include "pqxx/util.hxx" + + +namespace pqxx::internal +{ +template +constexpr inline auto const iterator_identity{ + [](decltype(*std::declval()) x) { return x; }}; + + +/// Marker type: pass a dynamically-determined number of statement parameters. +/** @deprecated Use @ref params instead. + * + * Normally when invoking a prepared or parameterised statement, the number + * of parameters is known at compile time. For instance, + * `t.exec_prepared("foo", 1, "x");` executes statement `foo` with two + * parameters, an `int` and a C string. + * + * But sometimes you may want to pass a number of parameters known only at run + * time. In those cases, a @ref dynamic_params encodes a dynamically + * determined number of parameters. You can mix these with regular, static + * parameter lists, and you can re-use them for multiple statement invocations. + * + * A dynamic_params object does not store copies of its parameters, so make + * sure they remain accessible until you've executed the statement. + * + * The ACCESSOR is an optional callable (such as a lambda). If you pass an + * accessor `a`, then each parameter `p` goes into your statement as `a(p)`. + */ +template)> +class dynamic_params +{ +public: + /// Wrap a sequence of pointers or iterators. + constexpr dynamic_params(IT begin, IT end) : + m_begin(begin), m_end(end), m_accessor(iterator_identity) + {} + + /// Wrap a sequence of pointers or iterators. + /** This version takes an accessor callable. If you pass an accessor `acc`, + * then any parameter `p` will go into the statement's parameter list as + * `acc(p)`. + */ + constexpr dynamic_params(IT begin, IT end, ACCESSOR &acc) : + m_begin(begin), m_end(end), m_accessor(acc) + {} + + /// Wrap a container. + template + explicit constexpr dynamic_params(C &container) : + dynamic_params(std::begin(container), std::end(container)) + {} + + /// Wrap a container. + /** This version takes an accessor callable. If you pass an accessor `acc`, + * then any parameter `p` will go into the statement's parameter list as + * `acc(p)`. + */ + template + explicit constexpr dynamic_params(C &container, ACCESSOR &acc) : + dynamic_params(std::begin(container), std::end(container), acc) + {} + + constexpr IT begin() const noexcept { return m_begin; } + constexpr IT end() const noexcept { return m_end; } + + constexpr auto access(decltype(*std::declval()) value) const + -> decltype(std::declval()(value)) + { + return m_accessor(value); + } + +private: + IT const m_begin, m_end; + ACCESSOR m_accessor = iterator_identity; +}; + + +/// Internal type: encode statement parameters. +/** Compiles arguments for prepared statements and parameterised queries into + * a format that can be passed into libpq. + * + * Objects of this type are meant to be short-lived: a `c_params` lives and + * dies entirely within the call to execute. So, for example, if you pass in a + * non-null pointer as a parameter, @ref params may simply use that pointer as + * a parameter value, without arranging longer-term storage for the data to + * which it points. All values referenced by parameters must remain "live" + * until the parameterised or prepared statement has been executed. + */ +struct PQXX_LIBEXPORT c_params +{ + c_params() = default; + /// Copying these objects is pointless and expensive. Don't do it. + c_params(c_params const &) = delete; + c_params(c_params &&) = default; + + /// Pre-allocate storage for `n` parameters. + void reserve(std::size_t n) &; + + /// As used by libpq: pointers to parameter values. + std::vector values; + /// As used by libpq: lengths of non-null arguments, in bytes. + std::vector lengths; + /// As used by libpq: effectively boolean "is this a binary parameter?" + std::vector formats; +}; +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/stream_iterator.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/stream_iterator.hxx new file mode 100644 index 000000000..f240dcfa7 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/stream_iterator.hxx @@ -0,0 +1,105 @@ +/** Stream iterators. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STREAM_ITERATOR +#define PQXX_H_STREAM_ITERATOR + +#include + +namespace pqxx +{ +class stream_from; +} + + +namespace pqxx::internal +{ +// C++20: Replace with generator? +/// Input iterator for stream_from. +/** Just barely enough to support range-based "for" loops. Don't assume that + * any of the usual behaviour works beyond that. + */ +template class stream_input_iterator +{ +public: + using value_type = std::tuple; + + /// Construct an "end" iterator. + stream_input_iterator() = default; + + explicit stream_input_iterator(stream_from &home) : m_home(&home) + { + advance(); + } + stream_input_iterator(stream_input_iterator const &) = default; + + stream_input_iterator &operator++() + { + advance(); + return *this; + } + + value_type const &operator*() const { return m_value; } + + /// Comparison only works for comparing to end(). + bool operator==(stream_input_iterator const &rhs) const + { + return m_home == rhs.m_home; + } + /// Comparison only works for comparing to end(). + bool operator!=(stream_input_iterator const &rhs) const + { + return not(*this == rhs); + } + +private: + void advance() + { + if (m_home == nullptr) + throw usage_error{"Moving stream_from iterator beyond end()."}; + if (not((*m_home) >> m_value)) + m_home = nullptr; + } + + stream_from *m_home{nullptr}; + value_type m_value; +}; + + +// C++20: Replace with generator? +/// Iteration over a @ref stream_from. +template class stream_input_iteration +{ +public: + using iterator = stream_input_iterator; + explicit stream_input_iteration(stream_from &home) : m_home{home} {} + iterator begin() const { return iterator{m_home}; } + iterator end() const { return {}; } + +private: + stream_from &m_home; +}; + + +// C++20: Replace with generator? +/// Iteration over a @ref stream_from, deleting it once done. +template class owning_stream_input_iteration +{ +public: + using iterator = stream_input_iterator; + explicit owning_stream_input_iteration(std::unique_ptr &&home) : + m_home{std::move(home)} + {} + iterator begin() const { return iterator{*m_home.get()}; } + iterator end() const { return {}; } + +private: + std::unique_ptr m_home; +}; +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/wait.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/wait.hxx new file mode 100644 index 000000000..7a82e6553 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/internal/wait.hxx @@ -0,0 +1,18 @@ +#if !defined(PQXX_WAIT_HXX) +# define PQXX_WAIT_HXX + +namespace pqxx::internal +{ +/// Wait. +/** This is normally `std::this_thread::sleep_for()`. But MinGW's `thread` + * header doesn't work, so we must be careful about including it. + */ +void PQXX_LIBEXPORT wait_for(unsigned int microseconds); + + +/// Wait for a socket to be ready for reading/writing, or timeout. +PQXX_LIBEXPORT void wait_fd( + int fd, bool for_read, bool for_write, unsigned seconds = 1, + unsigned microseconds = 0); +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation new file mode 100644 index 000000000..1b801329b --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation @@ -0,0 +1,8 @@ +/** Transaction isolation levels. + * + * Policies and traits describing SQL transaction isolation levels + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/isolation.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation.hxx new file mode 100644 index 000000000..0698c6ab4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/isolation.hxx @@ -0,0 +1,75 @@ +/* Definitions for transaction isolation levels, and such. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/isolation instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ISOLATION +#define PQXX_H_ISOLATION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/util.hxx" + +namespace pqxx +{ +/// Should a transaction be read-only, or read-write? +/** No, this is not an isolation level. So it really doesn't belong here. + * But it's not really worth a separate header. + */ +enum class write_policy +{ + read_only, + read_write +}; + + +/// Transaction isolation levels. +/** These are as defined in the SQL standard. But there are a few notes + * specific to PostgreSQL. + * + * First, postgres does not support "read uncommitted." The lowest level you + * can get is "read committed," which is better. PostgreSQL is built on the + * MVCC paradigm, which guarantees "read committed" isolation without any + * additional performance overhead, so there was no point in providing the + * lower level. + * + * Second, "repeatable read" also makes more isolation guarantees than the + * standard requires. According to the standard, this level prevents "dirty + * reads" and "nonrepeatable reads," but not "phantom reads." In postgres, + * it actually prevents all three. + * + * Third, "serializable" is only properly supported starting at postgres 9.1. + * If you request "serializable" isolation on an older backend, you will get + * the same isolation as in "repeatable read." It's better than the + * "repeatable read" defined in the SQL standard, but not a complete + * implementation of the standard's "serializable" isolation level. + * + * In general, a lower isolation level will allow more surprising interactions + * between ongoing transactions, but improve performance. A higher level + * gives you more protection from subtle concurrency bugs, but sometimes it + * may not be possible to complete your transaction without avoiding paradoxes + * in the data. In that case a transaction may fail, and the application will + * have to re-do the whole thing based on the latest state of the database. + * (If you want to retry your code in that situation, have a look at the + * transactor framework.) + * + * Study the levels and design your application with the right level in mind. + */ +enum isolation_level +{ + // PostgreSQL only has the better isolation levels. + // read_uncommitted, + + read_committed, + repeatable_read, + serializable, +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject new file mode 100644 index 000000000..1f2f94790 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject @@ -0,0 +1,8 @@ +/** Large Objects interface. + * + * Supports direct access to large objects, as well as through I/O streams + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/largeobject.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject.hxx new file mode 100644 index 000000000..ebafc51d8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/largeobject.hxx @@ -0,0 +1,735 @@ +/* Large Objects interface. Deprecated; use blob instead. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/largeobject instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_LARGEOBJECT +#define PQXX_H_LARGEOBJECT + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/dbtransaction.hxx" + + +namespace pqxx +{ +/// Identity of a large object. +/** @deprecated Use the @ref blob class instead. + * + * Encapsulates the identity of a large object. + * + * A largeobject must be accessed only from within a backend transaction, but + * the object's identity remains valid as long as the object exists. + */ +class PQXX_LIBEXPORT largeobject +{ +public: + using size_type = large_object_size_type; + + /// Refer to a nonexistent large object (similar to what a null pointer + /// does). + [[deprecated("Use blob instead.")]] largeobject() noexcept = default; + + /// Create new large object. + /** @param t Backend transaction in which the object is to be created. + */ + [[deprecated("Use blob instead.")]] explicit largeobject(dbtransaction &t); + + /// Wrap object with given oid. + /** Convert combination of a transaction and object identifier into a + * large object identity. Does not affect the database. + * @param o Object identifier for the given object. + */ + [[deprecated("Use blob instead.")]] explicit largeobject(oid o) noexcept : + m_id{o} + {} + + /// Import large object from a local file. + /** Creates a large object containing the data found in the given file. + * @param t Backend transaction in which the large object is to be created. + * @param file A filename on the client program's filesystem. + */ + [[deprecated("Use blob instead.")]] largeobject( + dbtransaction &t, std::string_view file); + + /// Take identity of an opened large object. + /** Copy identity of already opened large object. Note that this may be done + * as an implicit conversion. + * @param o Already opened large object to copy identity from. + */ + [[deprecated("Use blob instead.")]] largeobject( + largeobjectaccess const &o) noexcept; + + /// Object identifier. + /** The number returned by this function identifies the large object in the + * database we're connected to (or oid_none is returned if we refer to the + * null object). + */ + [[nodiscard]] oid id() const noexcept { return m_id; } + + /** + * @name Identity comparisons + * + * These operators compare the object identifiers of large objects. This has + * nothing to do with the objects' actual contents; use them only for keeping + * track of containers of references to large objects and such. + */ + //@{ + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator==(largeobject const &other) const + { + return m_id == other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator!=(largeobject const &other) const + { + return m_id != other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator<=(largeobject const &other) const + { + return m_id <= other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator>=(largeobject const &other) const + { + return m_id >= other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator<(largeobject const &other) const + { + return m_id < other.m_id; + } + /// Compare object identities + /** @warning Only valid between large objects in the same database. */ + [[nodiscard]] bool operator>(largeobject const &other) const + { + return m_id > other.m_id; + } + //@} + + /// Export large object's contents to a local file + /** Writes the data stored in the large object to the given file. + * @param t Transaction in which the object is to be accessed + * @param file A filename on the client's filesystem + */ + void to_file(dbtransaction &t, std::string_view file) const; + + /// Delete large object from database + /** Unlike its low-level equivalent cunlink, this will throw an exception if + * deletion fails. + * @param t Transaction in which the object is to be deleted + */ + void remove(dbtransaction &t) const; + +protected: + PQXX_PURE static internal::pq::PGconn * + raw_connection(dbtransaction const &T); + + PQXX_PRIVATE std::string reason(connection const &, int err) const; + +private: + oid m_id = oid_none; +}; + + +/// Accessor for large object's contents. +/** @deprecated Use the `blob` class instead. + */ +class PQXX_LIBEXPORT largeobjectaccess : private largeobject +{ +public: + using largeobject::size_type; + using off_type = size_type; + using pos_type = size_type; + + /// Open mode: `in`, `out` (can be combined using "bitwise or"). + /** According to the C++ standard, these should be in `std::ios_base`. We + * take them from derived class `std::ios` instead, which is easier on the + * eyes. + * + * Historical note: taking it from std::ios was originally a workaround for a + * problem with gcc 2.95. + */ + using openmode = std::ios::openmode; + + /// Default open mode: in, out, binary. + static constexpr auto default_mode{ + std::ios::in | std::ios::out | std::ios::binary}; + + /// Seek direction: `beg`, `cur`, `end`. + using seekdir = std::ios::seekdir; + + /// Create new large object and open it. + /** + * @param t Backend transaction in which the object is to be created. + * @param mode Access mode, defaults to ios_base::in | ios_base::out | + * ios_base::binary. + */ + [[deprecated("Use blob instead.")]] explicit largeobjectaccess( + dbtransaction &t, openmode mode = default_mode); + + /// Open large object with given oid. + /** Convert combination of a transaction and object identifier into a + * large object identity. Does not affect the database. + * @param t Transaction in which the object is to be accessed. + * @param o Object identifier for the given object. + * @param mode Access mode, defaults to ios_base::in | ios_base::out | + * ios_base::binary. + */ + [[deprecated("Use blob instead.")]] largeobjectaccess( + dbtransaction &t, oid o, openmode mode = default_mode); + + /// Open given large object. + /** Open a large object with the given identity for reading and/or writing. + * @param t Transaction in which the object is to be accessed. + * @param o Identity for the large object to be accessed. + * @param mode Access mode, defaults to ios_base::in | ios_base::out | + * ios_base::binary. + */ + [[deprecated("Use blob instead.")]] largeobjectaccess( + dbtransaction &t, largeobject o, openmode mode = default_mode); + + /// Import large object from a local file and open it. + /** Creates a large object containing the data found in the given file. + * @param t Backend transaction in which the large object is to be created. + * @param file A filename on the client program's filesystem. + * @param mode Access mode, defaults to ios_base::in | ios_base::out. + */ + [[deprecated("Use blob instead.")]] largeobjectaccess( + dbtransaction &t, std::string_view file, openmode mode = default_mode); + + ~largeobjectaccess() noexcept { close(); } + + /// Object identifier. + /** The number returned by this function uniquely identifies the large object + * in the context of the database we're connected to. + */ + using largeobject::id; + + /// Export large object's contents to a local file. + /** Writes the data stored in the large object to the given file. + * @param file A filename on the client's filesystem. + */ + void to_file(std::string_view file) const + { + largeobject::to_file(m_trans, file); + } + + using largeobject::to_file; + + /** + * @name High-level access to object contents. + */ + //@{ + /// Write data to large object. + /** @warning The size of a write is currently limited to 2GB. + * + * @param buf Data to write. + * @param len Number of bytes from Buf to write. + */ + void write(char const buf[], std::size_t len); + + /// Write string to large object. + /** If not all bytes could be written, an exception is thrown. + * @param buf Data to write; no terminating zero is written. + */ + void write(std::string_view buf) { write(std::data(buf), std::size(buf)); } + + /// Read data from large object. + /** Throws an exception if an error occurs while reading. + * @param buf Location to store the read data in. + * @param len Number of bytes to try and read. + * @return Number of bytes read, which may be less than the number requested + * if the end of the large object is reached. + */ + size_type read(char buf[], std::size_t len); + + /// Seek in large object's data stream. + /** Throws an exception if an error occurs. + * @return The new position in the large object + */ + size_type seek(size_type dest, seekdir dir); + + /// Report current position in large object's data stream. + /** Throws an exception if an error occurs. + * @return The current position in the large object. + */ + [[nodiscard]] size_type tell() const; + //@} + + /** + * @name Low-level access to object contents. + * + * These functions provide a more "C-like" access interface, returning + * special values instead of throwing exceptions on error. These functions + * are generally best avoided in favour of the high-level access functions, + * which behave more like C++ functions should. + * + * Due to libpq's underlying API, some operations are limited to "int" + * sizes, typically 2 GB, even though a large object can grow much larger. + */ + //@{ + /// Seek in large object's data stream. + /** Does not throw exception in case of error; inspect return value and + * `errno` instead. + * @param dest Offset to go to. + * @param dir Origin to which dest is relative: ios_base::beg (from beginning + * of the object), ios_base::cur (from current access position), or + * ios_base;:end (from end of object). + * @return New position in large object, or -1 if an error occurred. + */ + pos_type cseek(off_type dest, seekdir dir) noexcept; + + /// Write to large object's data stream. + /** Does not throw exception in case of error; inspect return value and + * `errno` instead. + * @param buf Data to write. + * @param len Number of bytes to write. + * @return Number of bytes actually written, or -1 if an error occurred. + */ + off_type cwrite(char const buf[], std::size_t len) noexcept; + + /// Read from large object's data stream. + /** Does not throw exception in case of error; inspect return value and + * `errno` instead. + * @param buf Area where incoming bytes should be stored. + * @param len Number of bytes to read. + * @return Number of bytes actually read, or -1 if an error occurred.. + */ + off_type cread(char buf[], std::size_t len) noexcept; + + /// Report current position in large object's data stream. + /** Does not throw exception in case of error; inspect return value and + * `errno` instead. + * @return Current position in large object, of -1 if an error occurred. + */ + [[nodiscard]] pos_type ctell() const noexcept; + //@} + + /** + * @name Error/warning output + */ + //@{ + /// Issue message to transaction's notice processor. + void process_notice(zview) noexcept; + //@} + + using largeobject::remove; + + using largeobject::operator==; + using largeobject::operator!=; + using largeobject::operator<; + using largeobject::operator<=; + using largeobject::operator>; + using largeobject::operator>=; + + largeobjectaccess() = delete; + largeobjectaccess(largeobjectaccess const &) = delete; + largeobjectaccess operator=(largeobjectaccess const &) = delete; + +private: + PQXX_PRIVATE std::string reason(int err) const; + internal::pq::PGconn *raw_connection() const + { + return largeobject::raw_connection(m_trans); + } + + PQXX_PRIVATE void open(openmode mode); + void close() noexcept; + + dbtransaction &m_trans; + int m_fd = -1; +}; + + +/// Streambuf to use large objects in standard I/O streams. +/** @deprecated Access large objects directly using the @ref blob class. + * + * The standard streambuf classes provide uniform access to data storage such + * as files or string buffers, so they can be accessed using standard input or + * output streams. This streambuf implementation provided similar access to + * large objects, so they could be read and written using the same stream + * classes. + * + * This functionality was considered too fragile and complex, so it has been + * replaced with a single, much simpler class. + */ +template> +class largeobject_streambuf : public std::basic_streambuf +{ + using size_type = largeobject::size_type; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + using openmode = largeobjectaccess::openmode; + using seekdir = largeobjectaccess::seekdir; + + /// Default open mode: in, out, binary. + static constexpr auto default_mode{ + std::ios::in | std::ios::out | std::ios::binary}; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + [[deprecated("Use blob instead.")]] largeobject_streambuf( + dbtransaction &t, largeobject o, openmode mode = default_mode, + size_type buf_size = 512) : + m_bufsize{buf_size}, m_obj{t, o, mode}, m_g{nullptr}, m_p{nullptr} + { + initialize(mode); + } +#include "pqxx/internal/ignore-deprecated-post.hxx" + + [[deprecated("Use blob instead.")]] largeobject_streambuf( + dbtransaction &t, oid o, openmode mode = default_mode, + size_type buf_size = 512) : + m_bufsize{buf_size}, m_obj{t, o, mode}, m_g{nullptr}, m_p{nullptr} + { + initialize(mode); + } + + virtual ~largeobject_streambuf() noexcept + { + delete[] m_p; + delete[] m_g; + } + + /// For use by large object stream classes. + void process_notice(zview const &s) { m_obj.process_notice(s); } + +protected: + virtual int sync() override + { + // setg() sets eback, gptr, egptr. + this->setg(this->eback(), this->eback(), this->egptr()); + return overflow(eof()); + } + + virtual pos_type seekoff(off_type offset, seekdir dir, openmode) override + { + return adjust_eof(m_obj.cseek(largeobjectaccess::off_type(offset), dir)); + } + + virtual pos_type seekpos(pos_type pos, openmode) override + { + largeobjectaccess::pos_type const newpos{ + m_obj.cseek(largeobjectaccess::off_type(pos), std::ios::beg)}; + return adjust_eof(newpos); + } + + virtual int_type overflow(int_type ch) override + { + auto *const pp{this->pptr()}; + if (pp == nullptr) + return eof(); + auto *const pb{this->pbase()}; + int_type res{0}; + + if (pp > pb) + { + auto const write_sz{pp - pb}; + auto const written_sz{ + m_obj.cwrite(pb, static_cast(pp - pb))}; + if (internal::cmp_less_equal(written_sz, 0)) + throw internal_error{ + "pqxx::largeobject: write failed " + "(is transaction still valid on write or flush?), " + "libpq reports error"}; + else if (write_sz != written_sz) + throw internal_error{ + "pqxx::largeobject: write failed " + "(is transaction still valid on write or flush?), " + + std::to_string(written_sz) + "/" + std::to_string(write_sz) + + " bytes written"}; + auto const out{adjust_eof(written_sz)}; + + if constexpr (std::is_arithmetic_v) + res = check_cast(out, "largeobject position"sv); + else + res = int_type(out); + } + this->setp(m_p, m_p + m_bufsize); + + // Write that one more character, if it's there. + if (ch != eof()) + { + *this->pptr() = static_cast(ch); + this->pbump(1); + } + return res; + } + + virtual int_type overflow() { return overflow(eof()); } + + virtual int_type underflow() override + { + if (this->gptr() == nullptr) + return eof(); + auto *const eb{this->eback()}; + auto const res{adjust_eof( + m_obj.cread(this->eback(), static_cast(m_bufsize)))}; + this->setg( + eb, eb, eb + (res == eof() ? 0 : static_cast(res))); + return (res == eof() or res == 0) ? eof() : traits_type::to_int_type(*eb); + } + +private: + /// Shortcut for traits_type::eof(). + static int_type eof() { return traits_type::eof(); } + + /// Helper: change error position of -1 to EOF (probably a no-op). + template static std::streampos adjust_eof(INTYPE pos) + { + bool const at_eof{pos == -1}; + if constexpr (std::is_arithmetic_v) + { + return check_cast( + (at_eof ? eof() : pos), "large object seek"sv); + } + else + { + return std::streampos(at_eof ? eof() : pos); + } + } + + void initialize(openmode mode) + { + if ((mode & std::ios::in) != 0) + { + m_g = new char_type[unsigned(m_bufsize)]; + this->setg(m_g, m_g, m_g); + } + if ((mode & std::ios::out) != 0) + { + m_p = new char_type[unsigned(m_bufsize)]; + this->setp(m_p, m_p + m_bufsize); + } + } + + size_type const m_bufsize; + largeobjectaccess m_obj; + + /// Get & put buffers. + char_type *m_g, *m_p; +}; + + +/// Input stream that gets its data from a large object. +/** @deprecated Access large objects directly using the @ref blob class. + * + * This class worked like any other istream, but to read data from a large + * object. It supported all formatting and streaming operations of + * `std::istream`. + * + * This functionality was considered too fragile and complex, so it has been + * replaced with a single, much simpler class. + */ +template> +class basic_ilostream : public std::basic_istream +{ + using super = std::basic_istream; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// Create a basic_ilostream. + /** + * @param t Transaction in which this stream is to exist. + * @param o Large object to access. + * @param buf_size Size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_ilostream( + dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{t, o, std::ios::in | std::ios::binary, buf_size} + { + super::init(&m_buf); + } +#include "pqxx/internal/ignore-deprecated-post.hxx" + + /// Create a basic_ilostream. + /** + * @param t Transaction in which this stream is to exist. + * @param o Identifier of a large object to access. + * @param buf_size Size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_ilostream( + dbtransaction &t, oid o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{t, o, std::ios::in | std::ios::binary, buf_size} + { + super::init(&m_buf); + } + +private: + largeobject_streambuf m_buf; +}; + +using ilostream = basic_ilostream; + + +/// Output stream that writes data back to a large object. +/** @deprecated Access large objects directly using the @ref blob class. + * + * This worked like any other ostream, but to write data to a large object. + * It supported all formatting and streaming operations of `std::ostream`. + * + * This functionality was considered too fragile and complex, so it has been + * replaced with a single, much simpler class. + */ +template> +class basic_olostream : public std::basic_ostream +{ + using super = std::basic_ostream; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// Create a basic_olostream. + /** + * @param t transaction in which this stream is to exist. + * @param o a large object to access. + * @param buf_size size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_olostream( + dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{t, o, std::ios::out | std::ios::binary, buf_size} + { + super::init(&m_buf); + } +#include "pqxx/internal/ignore-deprecated-post.hxx" + + /// Create a basic_olostream. + /** + * @param t transaction in which this stream is to exist. + * @param o a large object to access. + * @param buf_size size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_olostream( + dbtransaction &t, oid o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{t, o, std::ios::out | std::ios::binary, buf_size} + { + super::init(&m_buf); + } + + ~basic_olostream() + { + try + { + m_buf.pubsync(); + m_buf.pubsync(); + } + catch (std::exception const &e) + { + m_buf.process_notice(e.what()); + } + } + +private: + largeobject_streambuf m_buf; +}; + +using olostream = basic_olostream; + + +/// Stream that reads and writes a large object. +/** @deprecated Access large objects directly using the @ref blob class. + * + * This worked like a std::iostream, but to read data from, or write data to, a + * large object. It supported all formatting and streaming operations of + * `std::iostream`. + * + * This functionality was considered too fragile and complex, so it has been + * replaced with a single, much simpler class. + */ +template> +class basic_lostream : public std::basic_iostream +{ + using super = std::basic_iostream; + +public: + using char_type = CHAR; + using traits_type = TRAITS; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; + + /// Create a basic_lostream. + /** + * @param t Transaction in which this stream is to exist. + * @param o Large object to access. + * @param buf_size Size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_lostream( + dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{ + t, o, std::ios::in | std::ios::out | std::ios::binary, buf_size} + { + super::init(&m_buf); + } + + /// Create a basic_lostream. + /** + * @param t Transaction in which this stream is to exist. + * @param o Large object to access. + * @param buf_size Size of buffer to use internally (optional). + */ + [[deprecated("Use blob instead.")]] basic_lostream( + dbtransaction &t, oid o, largeobject::size_type buf_size = 512) : + super{nullptr}, + m_buf{ + t, o, std::ios::in | std::ios::out | std::ios::binary, buf_size} + { + super::init(&m_buf); + } + + ~basic_lostream() + { + try + { + m_buf.pubsync(); + m_buf.pubsync(); + } + catch (std::exception const &e) + { + m_buf.process_notice(e.what()); + } + } + +private: + largeobject_streambuf m_buf; +}; + +using lostream = basic_lostream; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction new file mode 100644 index 000000000..bb5b79724 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction @@ -0,0 +1,8 @@ +/** pqxx::nontransaction class. + * + * pqxx::nontransaction provides nontransactional database access. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/nontransaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction.hxx new file mode 100644 index 000000000..c50715594 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/nontransaction.hxx @@ -0,0 +1,76 @@ +/* Definition of the pqxx::nontransaction class. + * + * pqxx::nontransaction provides nontransactional database access + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/nontransaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_NONTRANSACTION +#define PQXX_H_NONTRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/connection.hxx" +#include "pqxx/result.hxx" +#include "pqxx/transaction.hxx" + +namespace pqxx +{ +using namespace std::literals; + +/// Simple "transaction" class offering no transactional integrity. +/** + * @ingroup transactions + * + * nontransaction, like transaction or any other transaction_base-derived + * class, provides access to a database through a connection. Unlike its + * siblings, however, nontransaction does not maintain any kind of + * transactional integrity. This may be useful eg. for read-only access to the + * database that does not require a consistent, atomic view on its data; or for + * operations that are not allowed within a backend transaction, such as + * creating tables. + * + * For queries that update the database, however, a real transaction is likely + * to be faster unless the transaction consists of only a single record update. + * + * Also, you can keep a nontransaction open for as long as you like. Actual + * back-end transactions are limited in lifespan, and will sometimes fail just + * because they took too long to execute or were left idle for too long. This + * will not happen with a nontransaction (although the connection may still + * time out, e.g. when the network is unavailable for a very long time). + * + * Any query executed in a nontransaction is committed immediately, and neither + * commit() nor abort() has any effect. + * + * Database features that require a backend transaction, such as cursors or + * large objects, will not work in a nontransaction. + */ +class PQXX_LIBEXPORT nontransaction final : public transaction_base +{ +public: + /// Constructor. + /** Create a "dummy" transaction. + * @param c Connection in which this "transaction" will operate. + * @param tname Optional tname for the transaction, beginning with a letter + * and containing only letters and digits. + */ + nontransaction(connection &c, std::string_view tname = ""sv) : + transaction_base{c, tname, std::shared_ptr{}} + { + register_transaction(); + } + + virtual ~nontransaction() override { close(); } + +private: + virtual void do_commit() override {} +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification new file mode 100644 index 000000000..a0bd1c73e --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification @@ -0,0 +1,8 @@ +/** pqxx::notification_receiver functor interface. + * + * pqxx::notification_receiver handles incoming notifications. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/notification.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification.hxx new file mode 100644 index 000000000..b59b8567a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/notification.hxx @@ -0,0 +1,94 @@ +/* Definition of the pqxx::notification_receiver functor interface. + * + * pqxx::notification_receiver handles incoming notifications. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/notification instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_NOTIFICATION +#define PQXX_H_NOTIFICATION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/types.hxx" + + +namespace pqxx +{ +/// "Observer" base class for notifications. +/** @addtogroup notification Notifications and Receivers + * + * To listen on a notification issued using the NOTIFY command, derive your own + * class from notification_receiver and define its function-call operator to + * perform whatever action you wish to take when the given notification + * arrives. Then create an object of that class and pass it to your connection. + * DO NOT use raw SQL to listen for notifications, or your attempts to listen + * won't be resumed when a connection fails--and you'll have no way to notice. + * + * Notifications never arrive inside a transaction, not even in a + * nontransaction. Therefore, you are free to open a transaction of your own + * inside your receiver's function invocation operator. + * + * Notifications you are listening for may arrive anywhere within libpqxx code, + * but be aware that **PostgreSQL defers notifications occurring inside + * transactions.** (This was done for excellent reasons; just think about what + * happens if the transaction where you happen to handle an incoming + * notification is later rolled back for other reasons). So if you're keeping + * a transaction open, don't expect any of your receivers on the same + * connection to be notified. + * + * (For very similar reasons, outgoing notifications are also not sent until + * the transaction that sends them commits.) + * + * Multiple receivers on the same connection may listen on a notification of + * the same name. An incoming notification is processed by invoking all + * receivers (zero or more) of the same name. + */ +class PQXX_LIBEXPORT PQXX_NOVTABLE notification_receiver +{ +public: + /// Register the receiver with a connection. + /** + * @param c Connnection to operate on. + * @param channel Name of the notification to listen for. + */ + notification_receiver(connection &c, std::string_view channel); + /// Register the receiver with a connection. + notification_receiver(notification_receiver const &) = delete; + /// Register the receiver with a connection. + notification_receiver &operator=(notification_receiver const &) = delete; + /// Deregister the receiver. + virtual ~notification_receiver(); + + /// The channel that this receiver listens on. + [[nodiscard]] std::string const &channel() const & { return m_channel; } + + // TODO: Change API to take payload as zview instead of string ref. + /// Overridable: action to invoke when notification arrives. + /** + * @param payload An optional string that may have been passed to the NOTIFY + * command. + * @param backend_pid Process ID of the database backend process that served + * our connection when the notification arrived. The actual process ID + * behind the connection may have changed by the time this method is called. + */ + virtual void operator()(std::string const &payload, int backend_pid) = 0; + +protected: + connection &conn() const noexcept { return m_conn; } + +private: + connection &m_conn; + std::string m_channel; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params new file mode 100644 index 000000000..4098782aa --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params @@ -0,0 +1,8 @@ +/** Helper classes for passing statement parameters. + * + * Use these for prepared statements and parameterised statements. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/params.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params.hxx new file mode 100644 index 000000000..2d29cdfed --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/params.hxx @@ -0,0 +1,383 @@ +/* Helpers for prepared statements and parameterised statements. + * + * See the connection class for more about such statements. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_PARAMS +#define PQXX_H_PARAMS + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/internal/concat.hxx" +#include "pqxx/internal/statement_parameters.hxx" +#include "pqxx/types.hxx" + + +/// @deprecated The new @ref params class replaces all of this. +namespace pqxx::prepare +{ +/// Pass a number of statement parameters only known at runtime. +/** @deprecated Use @ref params instead. + * + * When you call any of the `exec_params` functions, the number of arguments + * is normally known at compile time. This helper function supports the case + * where it is not. + * + * Use this function to pass a variable number of parameters, based on a + * sequence ranging from `begin` to `end` exclusively. + * + * The technique combines with the regular static parameters. You can use it + * to insert dynamic parameter lists in any place, or places, among the call's + * parameters. You can even insert multiple dynamic sequences. + * + * @param begin A pointer or iterator for iterating parameters. + * @param end A pointer or iterator for iterating parameters. + * @return An object representing the parameters. + */ +template +[[deprecated("Use the params class instead.")]] constexpr inline auto +make_dynamic_params(IT begin, IT end) +{ + return pqxx::internal::dynamic_params(begin, end); +} + + +/// Pass a number of statement parameters only known at runtime. +/** @deprecated Use @ref params instead. + * + * When you call any of the `exec_params` functions, the number of arguments + * is normally known at compile time. This helper function supports the case + * where it is not. + * + * Use this function to pass a variable number of parameters, based on a + * container of parameter values. + * + * The technique combines with the regular static parameters. You can use it + * to insert dynamic parameter lists in any place, or places, among the call's + * parameters. You can even insert multiple dynamic containers. + * + * @param container A container of parameter values. + * @return An object representing the parameters. + */ +template +[[deprecated("Use the params class instead.")]] constexpr inline auto +make_dynamic_params(C const &container) +{ + using IT = typename C::const_iterator; +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return pqxx::internal::dynamic_params{container}; +#include "pqxx/internal/ignore-deprecated-post.hxx" +} + + +/// Pass a number of statement parameters only known at runtime. +/** @deprecated Use @ref params instead. + * + * When you call any of the `exec_params` functions, the number of arguments + * is normally known at compile time. This helper function supports the case + * where it is not. + * + * Use this function to pass a variable number of parameters, based on a + * container of parameter values. + * + * The technique combines with the regular static parameters. You can use it + * to insert dynamic parameter lists in any place, or places, among the call's + * parameters. You can even insert multiple dynamic containers. + * + * @param container A container of parameter values. + * @param accessor For each parameter `p`, pass `accessor(p)`. + * @return An object representing the parameters. + */ +template +[[deprecated("Use the params class instead.")]] constexpr inline auto +make_dynamic_params(C &container, ACCESSOR accessor) +{ + using IT = decltype(std::begin(container)); +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return pqxx::internal::dynamic_params{container, accessor}; +#include "pqxx/internal/ignore-deprecated-post.hxx" +} +} // namespace pqxx::prepare + + +namespace pqxx +{ +/// Generate parameter placeholders for use in an SQL statement. +/** When you want to pass parameters to a prepared statement or a parameterised + * statement, you insert placeholders into the SQL. During invocation, the + * database replaces those with the respective parameter values you passed. + * + * The placeholders look like `$1` (for the first parameter value), `$2` (for + * the second), and so on. You can just write those directly in your + * statement. But for those rare cases where it becomes difficult to track + * which number a placeholder should have, you can use a `placeholders` object + * to count and generate them in order. + */ +template class placeholders +{ +public: + /// Maximum number of parameters we support. + static inline constexpr unsigned int max_params{ + (std::numeric_limits::max)()}; + + placeholders() + { + static constexpr auto initial{"$1\0"sv}; + initial.copy(std::data(m_buf), std::size(initial)); + } + + /// Read an ephemeral version of the current placeholder text. + /** @warning Changing the current placeholder number will overwrite this. + * Use the view immediately, or lose it. + */ + constexpr zview view() const &noexcept + { + return zview{std::data(m_buf), m_len}; + } + + /// Read the current placeholder text, as a `std::string`. + /** This will be slightly slower than converting to a `zview`. With most + * C++ implementations however, until you get into ridiculous numbers of + * parameters, the string will benefit from the Short String Optimization, or + * SSO. + */ + std::string get() const { return std::string(std::data(m_buf), m_len); } + + /// Move on to the next parameter. + void next() & + { + if (m_current >= max_params) + throw range_error{pqxx::internal::concat( + "Too many parameters in one statement: limit is ", max_params, ".")}; + ++m_current; + if (m_current % 10 == 0) + { + // Carry the 1. Don't get too clever for this relatively rare + // case, just rewrite the entire number. Leave the $ in place + // though. + char *const data{std::data(m_buf)}; + char *const end{string_traits::into_buf( + data + 1, data + std::size(m_buf), m_current)}; + // (Subtract because we don't include the trailing zero.) + m_len = check_cast(end - data, "placeholders counter") - 1; + } + else + { + PQXX_LIKELY + // Shortcut for the common case: just increment that last digit. + ++m_buf[m_len - 1]; + } + } + + /// Return the current placeholder number. The initial placeholder is 1. + COUNTER count() const noexcept { return m_current; } + +private: + /// Current placeholder number. Starts at 1. + COUNTER m_current = 1; + + /// Length of the current placeholder string, not including trailing zero. + COUNTER m_len = 2; + + /// Text buffer where we render the placeholders, with a trailing zero. + /** We keep reusing this for every subsequent placeholder, just because we + * don't like string allocations. + * + * Maximum length is the maximum base-10 digits that COUNTER can fully + * represent, plus 1 more for the extra digit that it can only partially + * fill up, plus room for the dollar sign and the trailing zero. + */ + std::array::digits10 + 3> m_buf; +}; + + +/// Build a parameter list for a parameterised or prepared statement. +/** When calling a parameterised statement or a prepared statement, you can + * pass parameters into the statement directly in the invocation, as + * additional arguments to `exec_prepared` or `exec_params`. But in + * complex cases, sometimes that's just not convenient. + * + * In those situations, you can create a `params` and append your parameters + * into that, one by one. Then you pass the `params` to `exec_prepared` or + * `exec_params`. + * + * Combinations also work: if you have a `params` containing a string + * parameter, and you call `exec_params` with an `int` argument followed by + * your `params`, you'll be passing the `int` as the first parameter and + * the string as the second. You can even insert a `params` in a `params`, + * or pass two `params` objects to a statement. + */ +class PQXX_LIBEXPORT params +{ +public: + params() = default; + + /// Pre-populate a `params` with `args`. Feel free to add more later. + template constexpr params(Args &&...args) + { + reserve(sizeof...(args)); + append_pack(std::forward(args)...); + } + + /// Pre-allocate room for at least `n` parameters. + /** This is not needed, but it may improve efficiency. + * + * Reserve space if you're going to add parameters individually, and you've + * got some idea of how many there are going to be. It may save some + * memory re-allocations. + */ + void reserve(std::size_t n) &; + + // C++20: constexpr. + /// Get the number of parameters currently in this `params`. + [[nodiscard]] auto size() const noexcept { return m_params.size(); } + + // C++20: Use the vector's ssize() directly and go noexcept+constexpr. + /// Get the number of parameters (signed). + /** Unlike `size()`, this is not yet `noexcept`. That's because C++17's + * `std::vector` does not have a `ssize()` member function. These member + * functions are `noexcept`, but `std::size()` and `std::ssize()` are + * not. + */ + [[nodiscard]] auto ssize() const { return pqxx::internal::ssize(m_params); } + + /// Append a null value. + void append() &; + + /// Append a non-null zview parameter. + /** The underlying data must stay valid for as long as the `params` + * remains active. + */ + void append(zview) &; + + /// Append a non-null string parameter. + /** Copies the underlying data into internal storage. For best efficiency, + * use the @ref zview variant if you can, or `std::move()` + */ + void append(std::string const &) &; + + /// Append a non-null string parameter. + void append(std::string &&) &; + + /// Append a non-null binary parameter. + /** The underlying data must stay valid for as long as the `params` + * remains active. + */ + void append(std::basic_string_view) &; + + /// Append a non-null binary parameter. + /** Copies the underlying data into internal storage. For best efficiency, + * use the `std::basic_string_view` variant if you can, or + * `std::move()`. + */ + void append(std::basic_string const &) &; + +#if defined(PQXX_HAVE_CONCEPTS) + /// Append a non-null binary parameter. + /** The `data` object must stay in place and unchanged, for as long as the + * `params` remains active. + */ + template void append(DATA const &data) & + { + append( + std::basic_string_view{std::data(data), std::size(data)}); + } +#endif // PQXX_HAVE_CONCEPTS + + /// Append a non-null binary parameter. + void append(std::basic_string &&) &; + + /// @deprecated Append binarystring parameter. + /** The binarystring must stay valid for as long as the `params` remains + * active. + */ + void append(binarystring const &value) &; + + /// Append all parameters from value. + template + void append(pqxx::internal::dynamic_params const &value) & + { + for (auto ¶m : value) append(value.access(param)); + } + + void append(params const &value) &; + + void append(params &&value) &; + + /// Append a non-null parameter, converting it to its string + /// representation. + template void append(TYPE const &value) & + { + // TODO: Pool storage for multiple string conversions in one buffer? + if constexpr (nullness>::always_null) + { + ignore_unused(value); + m_params.emplace_back(); + } + else if (is_null(value)) + { + m_params.emplace_back(); + } + else + { + m_params.emplace_back(entry{to_string(value)}); + } + } + + /// Append all elements of `range` as parameters. + template void append_multi(RANGE const &range) & + { +#if defined(PQXX_HAVE_CONCEPTS) + if constexpr (std::ranges::sized_range) + reserve(std::size(*this) + std::size(range)); +#endif + for (auto &value : range) append(value); + } + + /// For internal use: Generate a `params` object for use in calls. + /** The params object encapsulates the pointers which we will need to pass + * to libpq when calling a parameterised or prepared statement. + * + * The pointers in the params will refer to storage owned by either the + * params object, or the caller. This is not a problem because a + * `c_params` object is guaranteed to live only while the call is going on. + * As soon as we climb back out of that call tree, we're done with that + * data. + */ + pqxx::internal::c_params make_c_params() const; + +private: + /// Recursively append a pack of params. + template + void append_pack(Arg &&arg, More &&...args) + { + this->append(std::forward(arg)); + // Recurse for remaining args. + append_pack(std::forward(args)...); + } + + /// Terminating case: append an empty parameter pack. It's not hard BTW. + constexpr void append_pack() noexcept {} + + // The way we store a parameter depends on whether it's binary or text + // (most types are text), and whether we're responsible for storing the + // contents. + using entry = std::variant< + std::nullptr_t, zview, std::string, std::basic_string_view, + std::basic_string>; + std::vector m_params; + + static constexpr std::string_view s_overflow{ + "Statement parameter length overflow."sv}; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline new file mode 100644 index 000000000..bf828843a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline @@ -0,0 +1,8 @@ +/** pqxx::pipeline class. + * + * Throughput-optimized query interface. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/pipeline.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline.hxx new file mode 100644 index 000000000..049dcdd58 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pipeline.hxx @@ -0,0 +1,237 @@ +/* Definition of the pqxx::pipeline class. + * + * Throughput-optimized mechanism for executing queries. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/pipeline instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_PIPELINE +#define PQXX_H_PIPELINE + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#include "pqxx/transaction_base.hxx" + + +namespace pqxx +{ +// TODO: libpq 14 introduced a similar "pipeline mode." Can we use that? + +/// Processes several queries in FIFO manner, optimized for high throughput. +/** Use a pipeline if you want to keep doing useful work while your queries are + * executing. Result retrieval is decoupled from execution request; queries + * "go in at the front" and results "come out the back." + * + * Actually, you can retrieve the results in any order if you want, but it may + * lead to surprising "time travel" effects if any of the queries fails. In + * particular, syntax errors in the queries can confuse things and show up too + * early in the stream of results. + * + * Generally, if any of the queries fails, it will throw an exception at the + * point where you request its result. But it may happen earlier, especially + * if you request results out of chronological order. + * + * @warning While a pipeline is active, you cannot execute queries, open + * streams, etc. on the same transaction. A transaction can have at most one + * object of a type derived from @ref pqxx::transaction_focus active on it at a + * time. + */ +class PQXX_LIBEXPORT pipeline : public transaction_focus +{ +public: + /// Identifying numbers for queries. + using query_id = long; + + pipeline(pipeline const &) = delete; + pipeline &operator=(pipeline const &) = delete; + + /// Start a pipeline. + explicit pipeline(transaction_base &t) : transaction_focus{t, s_classname} + { + init(); + } + /// Start a pipeline. Assign it a name, for more helpful error messages. + pipeline(transaction_base &t, std::string_view tname) : + transaction_focus{t, s_classname, tname} + { + init(); + } + + /// Close the pipeline. + ~pipeline() noexcept; + + /// Add query to the pipeline. + /** Queries accumulate in the pipeline, which sends them to the backend in a + * batch separated by semicolons. The queries you insert must not use this + * trick themselves, or the pipeline will get hopelessly confused! + * + * @return Identifier for this query, unique only within this pipeline. + */ + query_id insert(std::string_view) &; + + /// Wait for all ongoing or pending operations to complete, and detach. + /** Detaches from the transaction when done. + * + * This does not produce the queries' results, so it may not report any + * errors which may have occurred in their execution. To be sure that your + * statements succeeded, call @ref retrieve until the pipeline is empty. + */ + void complete(); + + /// Forget all ongoing or pending operations and retrieved results. + /** Queries already sent to the backend may still be completed, depending + * on implementation and timing. + * + * Any error state (unless caused by an internal error) will also be cleared. + * This is mostly useful in a nontransaction, since a backend transaction is + * aborted automatically when an error occurs. + * + * Detaches from the transaction when done. + */ + void flush(); + + /// Cancel ongoing query, if any. + /** May cancel any or all of the queries that have been inserted at this + * point whose results have not yet been retrieved. If the pipeline lives in + * a backend transaction, that transaction may be left in a nonfunctional + * state in which it can only be aborted. + * + * Therefore, either use this function in a nontransaction, or abort the + * transaction after calling it. + */ + void cancel(); + + /// Is result for given query available? + [[nodiscard]] bool is_finished(query_id) const; + + /// Retrieve result for given query. + /** If the query failed for whatever reason, this will throw an exception. + * The function will block if the query has not finished yet. + * @warning If results are retrieved out-of-order, i.e. in a different order + * than the one in which their queries were inserted, errors may "propagate" + * to subsequent queries. + */ + result retrieve(query_id qid) + { + return retrieve(m_queries.find(qid)).second; + } + + /// Retrieve oldest unretrieved result (possibly wait for one). + /** @return The query's identifier and its result set. */ + std::pair retrieve(); + + [[nodiscard]] bool empty() const noexcept { return std::empty(m_queries); } + + /// Set maximum number of queries to retain before issuing them to the + /// backend. + /** The pipeline will perform better if multiple queries are issued at once, + * but retaining queries until the results are needed (as opposed to issuing + * them to the backend immediately) may negate any performance benefits the + * pipeline can offer. + * + * Recommended practice is to set this value no higher than the number of + * queries you intend to insert at a time. + * @param retain_max A nonnegative "retention capacity;" passing zero will + * cause queries to be issued immediately + * @return Old retention capacity + */ + int retain(int retain_max = 2) &; + + + /// Resume retained query emission. Harmless when not needed. + void resume() &; + +private: + struct PQXX_PRIVATE Query + { + explicit Query(std::string_view q) : + query{std::make_shared(q)} + {} + + std::shared_ptr query; + result res; + }; + + using QueryMap = std::map; + + void init(); + void attach(); + void detach(); + + /// Upper bound to query id's. + static constexpr query_id qid_limit() noexcept + { + // Parenthesise this to work around an eternal Visual C++ problem: + // Without the extra parentheses, unless NOMINMAX is defined, the + // preprocessor will mistake this "max" for its annoying built-in macro + // of the same name. + return (std::numeric_limits::max)(); + } + + /// Create new query_id. + PQXX_PRIVATE query_id generate_id(); + + bool have_pending() const noexcept + { + return m_issuedrange.second != m_issuedrange.first; + } + + PQXX_PRIVATE void issue(); + + /// The given query failed; never issue anything beyond that. + void set_error_at(query_id qid) noexcept + { + PQXX_UNLIKELY + if (qid < m_error) + m_error = qid; + } + + /// Throw pqxx::internal_error. + [[noreturn]] PQXX_PRIVATE void internal_error(std::string const &err); + + PQXX_PRIVATE bool obtain_result(bool expect_none = false); + + PQXX_PRIVATE void obtain_dummy(); + PQXX_PRIVATE void get_further_available_results(); + PQXX_PRIVATE void check_end_results(); + + /// Receive any results that happen to be available; it's not urgent. + PQXX_PRIVATE void receive_if_available(); + + /// Receive results, up to stop if possible. + PQXX_PRIVATE void receive(pipeline::QueryMap::const_iterator stop); + std::pair retrieve(pipeline::QueryMap::iterator); + + QueryMap m_queries; + std::pair m_issuedrange; + int m_retain = 0; + int m_num_waiting = 0; + query_id m_q_id = 0; + + /// Is there a "dummy query" pending? + bool m_dummy_pending = false; + + /// Point at which an error occurred; no results beyond it will be available + query_id m_error = qid_limit(); + + /// Encoding. + /** We store this in the object to avoid the risk of exceptions at awkward + * moments. + */ + internal::encoding_group m_encoding; + + static constexpr std::string_view s_classname{"pipeline"}; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pqxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pqxx new file mode 100644 index 000000000..17a8eaa9c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/pqxx @@ -0,0 +1,28 @@ +/// Convenience header: include all libpqxx definitions. +#include "pqxx/internal/header-pre.hxx" + +#include "pqxx/array.hxx" +#include "pqxx/binarystring.hxx" +#include "pqxx/blob.hxx" +#include "pqxx/connection.hxx" +#include "pqxx/cursor.hxx" +#include "pqxx/errorhandler.hxx" +#include "pqxx/except.hxx" +#include "pqxx/largeobject.hxx" +#include "pqxx/nontransaction.hxx" +#include "pqxx/notification.hxx" +#include "pqxx/params.hxx" +#include "pqxx/pipeline.hxx" +#include "pqxx/prepared_statement.hxx" +#include "pqxx/result.hxx" +#include "pqxx/internal/result_iterator.hxx" +#include "pqxx/internal/result_iter.hxx" +#include "pqxx/robusttransaction.hxx" +#include "pqxx/row.hxx" +#include "pqxx/stream_from.hxx" +#include "pqxx/stream_to.hxx" +#include "pqxx/subtransaction.hxx" +#include "pqxx/transaction.hxx" +#include "pqxx/transactor.hxx" + +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement new file mode 100644 index 000000000..674be7090 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement @@ -0,0 +1,3 @@ +/// @deprecated Include @c instead. + +#include "params.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement.hxx new file mode 100644 index 000000000..674be7090 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/prepared_statement.hxx @@ -0,0 +1,3 @@ +/// @deprecated Include @c instead. + +#include "params.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range new file mode 100644 index 000000000..11985eca4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range @@ -0,0 +1,6 @@ +/** Client-side support for SQL range types. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/range.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range.hxx new file mode 100644 index 000000000..dc480e4b7 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/range.hxx @@ -0,0 +1,515 @@ +#ifndef PQXX_H_RANGE +#define PQXX_H_RANGE + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include + +#include "pqxx/internal/array-composite.hxx" +#include "pqxx/internal/concat.hxx" + +namespace pqxx +{ +/// An _unlimited_ boundary value to a @ref pqxx::range. +/** Use this as a lower or upper bound for a range if the range should extend + * to infinity on that side. + * + * An unlimited boundary is always inclusive of "infinity" values, if the + * range's value type supports them. + */ +struct no_bound +{ + template constexpr bool extends_down_to(TYPE const &) const + { + return true; + } + template constexpr bool extends_up_to(TYPE const &) const + { + return true; + } +}; + + +/// An _inclusive_ boundary value to a @ref pqxx::range. +/** Use this as a lower or upper bound for a range if the range should include + * the value. + */ +template class inclusive_bound +{ +public: + inclusive_bound() = delete; + explicit inclusive_bound(TYPE const &value) : m_value{value} + { + if (is_null(value)) + throw argument_error{"Got null value as an inclusive range bound."}; + } + + [[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Would this bound, as a lower bound, include value? + [[nodiscard]] bool extends_down_to(TYPE const &value) const + { + return not(value < m_value); + } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Would this bound, as an upper bound, include value? + [[nodiscard]] bool extends_up_to(TYPE const &value) const + { + return not(m_value < value); + } + +private: + TYPE m_value; +}; + + +/// An _exclusive_ boundary value to a @ref pqxx::range. +/** Use this as a lower or upper bound for a range if the range should _not_ + * include the value. + */ +template class exclusive_bound +{ +public: + exclusive_bound() = delete; + explicit exclusive_bound(TYPE const &value) : m_value{value} + { + if (is_null(value)) + throw argument_error{"Got null value as an exclusive range bound."}; + } + + [[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Would this bound, as a lower bound, include value? + [[nodiscard]] bool extends_down_to(TYPE const &value) const + { + return m_value < value; + } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Would this bound, as an upper bound, include value? + [[nodiscard]] bool extends_up_to(TYPE const &value) const + { + return value < m_value; + } + +private: + TYPE m_value; +}; + + +/// A range boundary value. +/** A range bound is either no bound at all; or an inclusive bound; or an + * exclusive bound. Pass one of the three to the constructor. + */ +template class range_bound +{ +public: + range_bound() = delete; + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(no_bound) : m_bound{} {} + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(inclusive_bound const &bound) : m_bound{bound} {} + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(exclusive_bound const &bound) : m_bound{bound} {} + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(range_bound const &) = default; + // TODO: constexpr and/or noexcept if underlying constructor supports it. + range_bound(range_bound &&) = default; + + // TODO: constexpr and/or noexcept if underlying operators support it. + bool operator==(range_bound const &rhs) const + { + if (this->is_limited()) + return ( + rhs.is_limited() and (this->is_inclusive() == rhs.is_inclusive()) and + (*this->value() == *rhs.value())); + else + return not rhs.is_limited(); + } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + bool operator!=(range_bound const &rhs) const { return not(*this == rhs); } + range_bound &operator=(range_bound const &) = default; + range_bound &operator=(range_bound &&) = default; + + /// Is this a finite bound? + constexpr bool is_limited() const noexcept + { + return not std::holds_alternative(m_bound); + } + + /// Is this boundary an inclusive one? + constexpr bool is_inclusive() const noexcept + { + return std::holds_alternative>(m_bound); + } + + /// Is this boundary an exclusive one? + constexpr bool is_exclusive() const noexcept + { + return std::holds_alternative>(m_bound); + } + + // TODO: constexpr/noexcept if underlying function supports it. + /// Would this bound, as a lower bound, include `value`? + bool extends_down_to(TYPE const &value) const + { + return std::visit( + [&value](auto const &bound) { return bound.extends_down_to(value); }, + m_bound); + } + + // TODO: constexpr/noexcept if underlying function supports it. + /// Would this bound, as an upper bound, include `value`? + bool extends_up_to(TYPE const &value) const + { + return std::visit( + [&value](auto const &bound) { return bound.extends_up_to(value); }, + m_bound); + } + + /// Return bound value, or `nullptr` if it's not limited. + [[nodiscard]] constexpr TYPE const *value() const &noexcept + { + return std::visit( + [](auto const &bound) noexcept { + using bound_t = std::decay_t; + if constexpr (std::is_same_v) + return static_cast(nullptr); + else + return &bound.get(); + }, + m_bound); + } + +private: + std::variant, exclusive_bound> m_bound; +}; + + +// C++20: Concepts for comparisons, construction, etc. +/// A C++ equivalent to PostgreSQL's range types. +/** You can use this as a client-side representation of a "range" in SQL. + * + * PostgreSQL defines several range types, differing in the data type over + * which they range. You can also define your own range types. + * + * Usually you'll want the server to deal with ranges. But on occasions where + * you need to work with them client-side, you may want to use @ref + * pqxx::range. (In cases where all you do is pass them along to the server + * though, it's not worth the complexity. In that case you might as well treat + * ranges as just strings.) + * + * For documentation on PostgreSQL's range types, see: + * https://www.postgresql.org/docs/current/rangetypes.html + * + * The value type must be copyable and default-constructible, and support the + * less-than (`<`) and equals (`==`) comparisons. Value initialisation must + * produce a consistent value. + */ +template class range +{ +public: + /// Create a range. + /** For each of the two bounds, pass a @ref no_bound, @ref inclusive_bound, + * or + * @ref exclusive_bound. + */ + range(range_bound lower, range_bound upper) : + m_lower{lower}, m_upper{upper} + { + if ( + lower.is_limited() and upper.is_limited() and + (*upper.value() < *lower.value())) + throw range_error{internal::concat( + "Range's lower bound (", *lower.value(), + ") is greater than its upper bound (", *upper.value(), ").")}; + } + + // TODO: constexpr and/or noexcept if underlying constructor supports it. + /// Create an empty range. + /** SQL has a separate literal to denote an empty range, but any range which + * encompasses no values is an empty range. + */ + range() : + m_lower{exclusive_bound{TYPE{}}}, + m_upper{exclusive_bound{TYPE{}}} + {} + + // TODO: constexpr and/or noexcept if underlying operators support it. + bool operator==(range const &rhs) const + { + return (this->lower_bound() == rhs.lower_bound() and + this->upper_bound() == rhs.upper_bound()) or + (this->empty() and rhs.empty()); + } + + // TODO: constexpr and/or noexcept if underlying operator supports it. + bool operator!=(range const &rhs) const { return !(*this == rhs); } + + range(range const &) = default; + range(range &&) = default; + range &operator=(range const &) = default; + range &operator=(range &&) = default; + + // TODO: constexpr and/or noexcept if underlying operator supports it. + /// Is this range clearly empty? + /** An empty range encompasses no values. + * + * It is possible to "fool" this. For example, if your range is of an + * integer type and has exclusive bounds of 0 and 1, it encompasses no values + * but its `empty()` will return false. The PostgreSQL implementation, by + * contrast, will notice that it is empty. Similar things can happen for + * floating-point types, but with more subtleties and edge cases. + */ + bool empty() const + { + return (m_lower.is_exclusive() or m_upper.is_exclusive()) and + m_lower.is_limited() and m_upper.is_limited() and + not(*m_lower.value() < *m_upper.value()); + } + + // TODO: constexpr and/or noexcept if underlying functions support it. + /// Does this range encompass `value`? + bool contains(TYPE value) const + { + return m_lower.extends_down_to(value) and m_upper.extends_up_to(value); + } + + // TODO: constexpr and/or noexcept if underlying operators support it. + /// Does this range encompass all of `other`? + /** This function is not particularly smart. It does not know, for example, + * that integer ranges `[0,9]` and `[0,10)` contain the same values. + */ + bool contains(range const &other) const + { + return (*this & other) == other; + } + + [[nodiscard]] constexpr range_bound const & + lower_bound() const &noexcept + { + return m_lower; + } + [[nodiscard]] constexpr range_bound const & + upper_bound() const &noexcept + { + return m_upper; + } + + // TODO: constexpr and/or noexcept if underlying operators support it. + /// Intersection of two ranges. + /** Returns a range describing those values which are in both ranges. + */ + range operator&(range const &other) const + { + range_bound lower{no_bound{}}; + if (not this->lower_bound().is_limited()) + lower = other.lower_bound(); + else if (not other.lower_bound().is_limited()) + lower = this->lower_bound(); + else if (*this->lower_bound().value() < *other.lower_bound().value()) + lower = other.lower_bound(); + else if (*other.lower_bound().value() < *this->lower_bound().value()) + lower = this->lower_bound(); + else if (this->lower_bound().is_exclusive()) + lower = this->lower_bound(); + else + lower = other.lower_bound(); + + range_bound upper{no_bound{}}; + if (not this->upper_bound().is_limited()) + upper = other.upper_bound(); + else if (not other.upper_bound().is_limited()) + upper = this->upper_bound(); + else if (*other.upper_bound().value() < *this->upper_bound().value()) + upper = other.upper_bound(); + else if (*this->upper_bound().value() < *other.upper_bound().value()) + upper = this->upper_bound(); + else if (this->upper_bound().is_exclusive()) + upper = this->upper_bound(); + else + upper = other.upper_bound(); + + if ( + lower.is_limited() and upper.is_limited() and + (*upper.value() < *lower.value())) + return {}; + else + return {lower, upper}; + } + + /// Convert to another base type. + template operator range() const + { + range_bound lower{no_bound{}}, upper{no_bound{}}; + if (lower_bound().is_inclusive()) + lower = inclusive_bound{*lower_bound().value()}; + else if (lower_bound().is_exclusive()) + lower = exclusive_bound{*lower_bound().value()}; + + if (upper_bound().is_inclusive()) + upper = inclusive_bound{*upper_bound().value()}; + else if (upper_bound().is_exclusive()) + upper = exclusive_bound{*upper_bound().value()}; + + return {lower, upper}; + } + +private: + range_bound m_lower, m_upper; +}; + + +/// String conversions for a @ref range type. +/** Conversion assumes that either your client encoding is UTF-8, or the values + * are pure ASCII. + */ +template struct string_traits> +{ + [[nodiscard]] static inline zview + to_buf(char *begin, char *end, range const &value) + { + return generic_to_buf(begin, end, value); + } + + static inline char * + into_buf(char *begin, char *end, range const &value) + { + if (value.empty()) + { + if ((end - begin) <= internal::ssize(s_empty)) + throw conversion_overrun{s_overrun.c_str()}; + char *here = begin + s_empty.copy(begin, std::size(s_empty)); + *here++ = '\0'; + return here; + } + else + { + if (end - begin < 4) + throw conversion_overrun{s_overrun.c_str()}; + char *here = begin; + *here++ = + (static_cast(value.lower_bound().is_inclusive() ? '[' : '(')); + TYPE const *lower{value.lower_bound().value()}; + // Convert bound (but go back to overwrite that trailing zero). + if (lower != nullptr) + here = string_traits::into_buf(here, end, *lower) - 1; + *here++ = ','; + TYPE const *upper{value.upper_bound().value()}; + // Convert bound (but go back to overwrite that trailing zero). + if (upper != nullptr) + here = string_traits::into_buf(here, end, *upper) - 1; + if ((end - here) < 2) + throw conversion_overrun{s_overrun.c_str()}; + *here++ = + static_cast(value.upper_bound().is_inclusive() ? ']' : ')'); + *here++ = '\0'; + return here; + } + } + + [[nodiscard]] static inline range from_string(std::string_view text) + { + if (std::size(text) < 3) + throw pqxx::conversion_error{err_bad_input(text)}; + bool left_inc{false}; + switch (text[0]) + { + case '[': left_inc = true; break; + + case '(': break; + + case 'e': + case 'E': + if ( + (std::size(text) != std::size(s_empty)) or + (text[1] != 'm' and text[1] != 'M') or + (text[2] != 'p' and text[2] != 'P') or + (text[3] != 't' and text[3] != 'T') or + (text[4] != 'y' and text[4] != 'Y')) + throw pqxx::conversion_error{err_bad_input(text)}; + return {}; + break; + + default: throw pqxx::conversion_error{err_bad_input(text)}; + } + + auto scan{internal::get_glyph_scanner(internal::encoding_group::UTF8)}; + // The field parser uses this to track which field it's parsing, and + // when not to expect a field separator. + std::size_t index{0}; + // The last field we expect to see. + static constexpr std::size_t last{1}; + // Current parsing position. We skip the opening parenthesis or bracket. + std::size_t pos{1}; + // The string may leave out either bound to indicate that it's unlimited. + std::optional lower, upper; + // We reuse the same field parser we use for composite values and arrays. + internal::parse_composite_field(index, text, pos, lower, scan, last); + internal::parse_composite_field(index, text, pos, upper, scan, last); + + // We need one more character: the closing parenthesis or bracket. + if (pos != std::size(text)) + throw pqxx::conversion_error{err_bad_input(text)}; + char const closing{text[pos - 1]}; + if (closing != ')' and closing != ']') + throw pqxx::conversion_error{err_bad_input(text)}; + bool const right_inc{closing == ']'}; + + range_bound lower_bound{no_bound{}}, upper_bound{no_bound{}}; + if (lower) + { + if (left_inc) + lower_bound = inclusive_bound{*lower}; + else + lower_bound = exclusive_bound{*lower}; + } + if (upper) + { + if (right_inc) + upper_bound = inclusive_bound{*upper}; + else + upper_bound = exclusive_bound{*upper}; + } + + return {lower_bound, upper_bound}; + } + + [[nodiscard]] static inline constexpr std::size_t + size_buffer(range const &value) noexcept + { + TYPE const *lower{value.lower_bound().value()}, + *upper{value.upper_bound().value()}; + std::size_t const lsz{ + lower == nullptr ? 0 : string_traits::size_buffer(*lower) - 1}, + usz{upper == nullptr ? 0 : string_traits::size_buffer(*upper) - 1}; + + if (value.empty()) + return std::size(s_empty) + 1; + else + return 1 + lsz + 1 + usz + 2; + } + +private: + static constexpr zview s_empty{"empty"_zv}; + static constexpr auto s_overrun{"Not enough space in buffer for range."_zv}; + + /// Compose error message for invalid range input. + static std::string err_bad_input(std::string_view text) + { + return internal::concat("Invalid range input: '", text, "'"); + } +}; + + +/// A range type does not have an innate null value. +template struct nullness> : no_null> +{}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result new file mode 100644 index 000000000..523394b72 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result @@ -0,0 +1,16 @@ +/** pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" + +#include "pqxx/result.hxx" + +// Now include some types which depend on result, but which the user will +// expect to see defined after including this header. +#include "pqxx/internal/result_iterator.hxx" +#include "pqxx/field.hxx" +#include "pqxx/internal/result_iter.hxx" + +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result.hxx new file mode 100644 index 000000000..6c41cc096 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/result.hxx @@ -0,0 +1,335 @@ +/* Definitions for the pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_RESULT +#define PQXX_H_RESULT + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include +#include + +#include "pqxx/except.hxx" +#include "pqxx/types.hxx" +#include "pqxx/util.hxx" +#include "pqxx/zview.hxx" + +#include "pqxx/internal/encodings.hxx" + + +namespace pqxx::internal +{ +// TODO: Make noexcept (but breaks ABI). +PQXX_LIBEXPORT void clear_result(pq::PGresult const *); +} // namespace pqxx::internal + + +namespace pqxx::internal::gate +{ +class result_connection; +class result_creation; +class result_pipeline; +class result_row; +class result_sql_cursor; +} // namespace pqxx::internal::gate + + +namespace pqxx +{ +/// Result set containing data returned by a query or command. +/** This behaves as a container (as defined by the C++ standard library) and + * provides random access const iterators to iterate over its rows. You can + * also access a row by indexing a `result R` by the row's zero-based + * number: + * + * + * for (result::size_type i=0; i < std::size(R); ++i) Process(R[i]); + * + * + * Result sets in libpqxx are lightweight, reference-counted wrapper objects + * which are relatively small and cheap to copy. Think of a result object as + * a "smart pointer" to an underlying result set. + * + * @warning The result set that a result object points to is not thread-safe. + * If you copy a result object, it still refers to the same underlying result + * set. So never copy, destroy, query, or otherwise access a result while + * another thread may be copying, destroying, querying, or otherwise accessing + * the same result set--even if it is doing so through a different result + * object! + */ +class PQXX_LIBEXPORT result +{ +public: + using size_type = result_size_type; + using difference_type = result_difference_type; + using reference = row; + using const_iterator = const_result_iterator; + using pointer = const_iterator; + using iterator = const_iterator; + using const_reverse_iterator = const_reverse_result_iterator; + using reverse_iterator = const_reverse_iterator; + + result() noexcept : + m_data{make_data_pointer()}, + m_query{}, + m_encoding{internal::encoding_group::MONOBYTE} + {} + + result(result const &rhs) noexcept = default; + result(result &&rhs) noexcept = default; + + /// Assign one result to another. + /** Copying results is cheap: it copies only smart pointers, but the actual + * data stays in the same place. + */ + result &operator=(result const &rhs) noexcept = default; + + /// Assign one result to another, invaliding the old one. + result &operator=(result &&rhs) noexcept = default; + + /** + * @name Comparisons + * + * You can compare results for equality. Beware: this is a very strict, + * dumb comparison. The smallest difference between two results (such as a + * string "Foo" versus a string "foo") will make them unequal. + */ + //@{ + /// Compare two results for equality. + [[nodiscard]] bool operator==(result const &) const noexcept; + /// Compare two results for inequality. + [[nodiscard]] bool operator!=(result const &rhs) const noexcept + { + return not operator==(rhs); + } + //@} + + /// Iterate rows, reading them directly into a tuple of "TYPE...". + /** Converts the fields to values of the given respective types. + * + * Use this only with a ranged "for" loop. The iteration produces + * std::tuple which you can "unpack" to a series of `auto` + * variables. + */ + template auto iter() const; + + [[nodiscard]] const_reverse_iterator rbegin() const; + [[nodiscard]] const_reverse_iterator crbegin() const; + [[nodiscard]] const_reverse_iterator rend() const; + [[nodiscard]] const_reverse_iterator crend() const; + + [[nodiscard]] const_iterator begin() const noexcept; + [[nodiscard]] const_iterator cbegin() const noexcept; + [[nodiscard]] inline const_iterator end() const noexcept; + [[nodiscard]] inline const_iterator cend() const noexcept; + + [[nodiscard]] reference front() const noexcept; + [[nodiscard]] reference back() const noexcept; + + [[nodiscard]] PQXX_PURE size_type size() const noexcept; + [[nodiscard]] PQXX_PURE bool empty() const noexcept; + [[nodiscard]] size_type capacity() const noexcept { return size(); } + + /// Exchange two `result` values in an exception-safe manner. + /** If the swap fails, the two values will be exactly as they were before. + * + * The swap is not necessarily thread-safe. + */ + void swap(result &) noexcept; + + /// Index a row by number. + /** This returns a @ref row object. Generally you should not keep the row + * around as a variable, but if you do, make sure that your variable is a + * `row`, not a `row&`. + */ + [[nodiscard]] row operator[](size_type i) const noexcept; + +#if defined(PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT) + // TODO: If C++23 will let us, also accept string for the column. + [[nodiscard]] field + operator[](size_type row_num, row_size_type col_num) const noexcept; +#endif + + /// Index a row by number, but check that the row number is valid. + row at(size_type) const; + + /// Index a field by row number and column number. + field at(size_type, row_size_type) const; + + /// Let go of the result's data. + /** Use this if you need to deallocate the result data earlier than you can + * destroy the `result` object itself. + * + * Multiple `result` objects can refer to the same set of underlying data. + * The underlying data will be deallocated once all `result` objects that + * refer to it are cleared or destroyed. + */ + void clear() noexcept + { + m_data.reset(); + m_query = nullptr; + } + + /** + * @name Column information + */ + //@{ + /// Number of columns in result. + [[nodiscard]] PQXX_PURE row_size_type columns() const noexcept; + + /// Number of given column (throws exception if it doesn't exist). + [[nodiscard]] row_size_type column_number(zview name) const; + + /// Name of column with this number (throws exception if it doesn't exist) + [[nodiscard]] char const *column_name(row_size_type number) const &; + + /// Return column's type, as an OID from the system catalogue. + [[nodiscard]] oid column_type(row_size_type col_num) const; + + /// Return column's type, as an OID from the system catalogue. + [[nodiscard]] oid column_type(zview col_name) const + { + return column_type(column_number(col_name)); + } + + /// What table did this column come from? + [[nodiscard]] oid column_table(row_size_type col_num) const; + + /// What table did this column come from? + [[nodiscard]] oid column_table(zview col_name) const + { + return column_table(column_number(col_name)); + } + + /// What column in its table did this column come from? + [[nodiscard]] row_size_type table_column(row_size_type col_num) const; + + /// What column in its table did this column come from? + [[nodiscard]] row_size_type table_column(zview col_name) const + { + return table_column(column_number(col_name)); + } + //@} + + /// Query that produced this result, if available (empty string otherwise) + [[nodiscard]] PQXX_PURE std::string const &query() const &noexcept; + + /// If command was an `INSERT` of 1 row, return oid of the inserted row. + /** @return Identifier of inserted row if exactly one row was inserted, or + * @ref oid_none otherwise. + */ + [[nodiscard]] PQXX_PURE oid inserted_oid() const; + + /// If command was `INSERT`, `UPDATE`, or `DELETE`: number of affected rows. + /** @return Number of affected rows if last command was `INSERT`, `UPDATE`, + * or `DELETE`; zero for all other commands. + */ + [[nodiscard]] PQXX_PURE size_type affected_rows() const; + + // C++20: Concept like std::invocable, but without specifying param types. + /// Run `func` on each row, passing the row's fields as parameters. + /** Goes through the rows from first to last. You provide a callable `func`. + * + * For each row in the `result`, `for_each` will call `func`. It converts + * the row's fields to the types of `func`'s parameters, and pass them to + * `func`. + * + * (Therefore `func` must have a _single_ signature. It can't be a generic + * lambda, or an object of a class with multiple overloaded function call + * operators. Otherwise, `for_each` will have no way to detect a parameter + * list without ambiguity.) + * + * If any of your parameter types is `std::string_view`, it refers to the + * underlying storage of this `result`. + * + * If any of your parameter types is a reference type, its argument will + * refer to a temporary value which only lives for the duration of that + * single invocation to `func`. If the reference is an lvalue reference, it + * must be `const`. + * + * For example, this queries employee names and salaries from the database + * and prints how much each would like to earn instead: + * ```cxx + * tx.exec("SELECT name, salary FROM employee").for_each( + * [](std::string_view name, float salary){ + * std::cout << name << " would like " << salary * 2 << ".\n"; + * }) + * ``` + * + * If `func` throws an exception, processing stops at that point and + * propagates the exception. + * + * @throws usage_error if `func`'s number of parameters does not match the + * number of columns in this result. + */ + template inline void for_each(CALLABLE &&func) const; + +private: + using data_pointer = std::shared_ptr; + + /// Underlying libpq result set. + data_pointer m_data; + + /// Factory for data_pointer. + static data_pointer + make_data_pointer(internal::pq::PGresult const *res = nullptr) noexcept + { + return {res, internal::clear_result}; + } + + friend class pqxx::internal::gate::result_pipeline; + PQXX_PURE std::shared_ptr query_ptr() const noexcept + { + return m_query; + } + + /// Query string. + std::shared_ptr m_query; + + internal::encoding_group m_encoding; + + static std::string const s_empty_string; + + friend class pqxx::field; + // TODO: noexcept. Breaks ABI. + PQXX_PURE char const *get_value(size_type row, row_size_type col) const; + // TODO: noexcept. Breaks ABI. + PQXX_PURE bool get_is_null(size_type row, row_size_type col) const; + PQXX_PURE + field_size_type get_length(size_type, row_size_type) const noexcept; + + friend class pqxx::internal::gate::result_creation; + result( + internal::pq::PGresult *rhs, std::shared_ptr query, + internal::encoding_group enc); + + PQXX_PRIVATE void check_status(std::string_view desc = ""sv) const; + + friend class pqxx::internal::gate::result_connection; + friend class pqxx::internal::gate::result_row; + bool operator!() const noexcept { return m_data.get() == nullptr; } + operator bool() const noexcept { return m_data.get() != nullptr; } + + [[noreturn]] PQXX_PRIVATE void + throw_sql_error(std::string const &Err, std::string const &Query) const; + PQXX_PRIVATE PQXX_PURE int errorposition() const; + PQXX_PRIVATE std::string status_error() const; + + friend class pqxx::internal::gate::result_sql_cursor; + PQXX_PURE char const *cmd_status() const noexcept; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction new file mode 100644 index 000000000..04b71d7cc --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction @@ -0,0 +1,8 @@ +/** pqxx::robusttransaction class. + * + * pqxx::robusttransaction is a slower but safer transaction class. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/robusttransaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction.hxx new file mode 100644 index 000000000..faf6dbf5e --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/robusttransaction.hxx @@ -0,0 +1,120 @@ +/* Definition of the pqxx::robusttransaction class. + * + * pqxx::robusttransaction is a slower but safer transaction class. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/robusttransaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ROBUSTTRANSACTION +#define PQXX_H_ROBUSTTRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/dbtransaction.hxx" + +namespace pqxx::internal +{ +/// Helper base class for the @ref robusttransaction class template. +class PQXX_LIBEXPORT PQXX_NOVTABLE basic_robusttransaction + : public dbtransaction +{ +public: + virtual ~basic_robusttransaction() override = 0; + +protected: + basic_robusttransaction( + connection &c, zview begin_command, std::string_view tname); + basic_robusttransaction(connection &c, zview begin_command); + +private: + using IDType = unsigned long; + + std::string m_conn_string; + std::string m_xid; + int m_backendpid = -1; + + void init(zview begin_command); + + // @warning This function will become `final`. + virtual void do_commit() override; +}; +} // namespace pqxx::internal + + +namespace pqxx +{ +/** + * @ingroup transactions + * + * @{ + */ + +/// Slightly slower, better-fortified version of transaction. +/** Requires PostgreSQL 10 or better. + * + * robusttransaction is similar to transaction, but spends more time and effort + * to deal with the hopefully rare case that the connection to the backend is + * lost just while it's trying to commit. In such cases, the client does not + * know whether the backend (on the other side of the broken connection) + * managed to commit the transaction. + * + * When this happens, robusttransaction tries to reconnect to the database and + * figure out what happened. + * + * This service level was made optional since you may not want to pay the + * overhead where it is not necessary. Certainly the use of this class makes + * no sense for local connections, or for transactions that read the database + * but never modify it, or for noncritical database manipulations. + * + * Besides being slower, it's also more complex. Which means that in practice + * a robusttransaction could actually fail more instead of less often than a + * normal transaction. What robusttransaction tries to achieve is to give you + * certainty, not just be more successful per se. + */ +template +class robusttransaction final : public internal::basic_robusttransaction +{ +public: + /** Create robusttransaction of given name. + * @param c Connection inside which this robusttransaction should live. + * @param tname optional human-readable name for this transaction. + */ + robusttransaction(connection &c, std::string_view tname) : + internal::basic_robusttransaction{ + c, pqxx::internal::begin_cmd, + tname} + {} + + /** Create robusttransaction of given name. + * @param c Connection inside which this robusttransaction should live. + * @param tname optional human-readable name for this transaction. + */ + robusttransaction(connection &c, std::string &&tname) : + internal::basic_robusttransaction{ + c, pqxx::internal::begin_cmd, + std::move(tname)} + {} + + /** Create robusttransaction of given name. + * @param c Connection inside which this robusttransaction should live. + */ + explicit robusttransaction(connection &c) : + internal::basic_robusttransaction{ + c, pqxx::internal::begin_cmd} + {} + + virtual ~robusttransaction() noexcept override { close(); } +}; + +/** + * @} + */ +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row new file mode 100644 index 000000000..62a950ac8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row @@ -0,0 +1,11 @@ +/** pqxx::row class. + * + * pqxx::row refers to a row in a result. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" + +#include "pqxx/result.hxx" +#include "pqxx/row.hxx" + +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row.hxx new file mode 100644 index 000000000..5be5132e3 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/row.hxx @@ -0,0 +1,561 @@ +/* Definitions for the pqxx::result class and support classes. + * + * pqxx::result represents the set of result rows from a database query. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ROW +#define PQXX_H_ROW + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/except.hxx" +#include "pqxx/field.hxx" +#include "pqxx/result.hxx" + +#include "pqxx/internal/concat.hxx" + +namespace pqxx::internal +{ +template class result_iter; +} // namespace pqxx::internal + + +namespace pqxx +{ +/// Reference to one row in a result. +/** A row represents one row (also called a row) in a query result set. + * It also acts as a container mapping column numbers or names to field + * values (see below): + * + * ```cxx + * cout << row["date"].c_str() << ": " << row["name"].c_str() << endl; + * ``` + * + * The row itself acts like a (non-modifyable) container, complete with its + * own const_iterator and const_reverse_iterator. + */ +class PQXX_LIBEXPORT row +{ +public: + using size_type = row_size_type; + using difference_type = row_difference_type; + using const_iterator = const_row_iterator; + using iterator = const_iterator; + using reference = field; + using pointer = const_row_iterator; + using const_reverse_iterator = const_reverse_row_iterator; + using reverse_iterator = const_reverse_iterator; + + row() noexcept = default; + row(row &&) noexcept = default; + row(row const &) noexcept = default; + row &operator=(row const &) noexcept = default; + row &operator=(row &&) noexcept = default; + + /** + * @name Comparison + */ + //@{ + [[nodiscard]] PQXX_PURE bool operator==(row const &) const noexcept; + [[nodiscard]] bool operator!=(row const &rhs) const noexcept + { + return not operator==(rhs); + } + //@} + + [[nodiscard]] const_iterator begin() const noexcept; + [[nodiscard]] const_iterator cbegin() const noexcept; + [[nodiscard]] const_iterator end() const noexcept; + [[nodiscard]] const_iterator cend() const noexcept; + + /** + * @name Field access + */ + //@{ + [[nodiscard]] reference front() const noexcept; + [[nodiscard]] reference back() const noexcept; + + // TODO: noexcept. Breaks ABI. + [[nodiscard]] const_reverse_row_iterator rbegin() const; + // TODO: noexcept. Breaks ABI. + [[nodiscard]] const_reverse_row_iterator crbegin() const; + // TODO: noexcept. Breaks ABI. + [[nodiscard]] const_reverse_row_iterator rend() const; + // TODO: noexcept. Breaks ABI. + [[nodiscard]] const_reverse_row_iterator crend() const; + + [[nodiscard]] reference operator[](size_type) const noexcept; + /** Address field by name. + * @warning This is much slower than indexing by number, or iterating. + */ + [[nodiscard]] reference operator[](zview col_name) const; + + reference at(size_type) const; + /** Address field by name. + * @warning This is much slower than indexing by number, or iterating. + */ + reference at(zview col_name) const; + + [[nodiscard]] constexpr size_type size() const noexcept + { + return m_end - m_begin; + } + + [[deprecated("Swap iterators, not rows.")]] void swap(row &) noexcept; + + /// Row number, assuming this is a real row and not end()/rend(). + [[nodiscard]] constexpr result::size_type rownumber() const noexcept + { + return m_index; + } + + /** + * @name Column information + */ + //@{ + /// Number of given column (throws exception if it doesn't exist). + [[nodiscard]] size_type column_number(zview col_name) const; + + /// Return a column's type. + [[nodiscard]] oid column_type(size_type) const; + + /// Return a column's type. + [[nodiscard]] oid column_type(zview col_name) const + { + return column_type(column_number(col_name)); + } + + /// What table did this column come from? + [[nodiscard]] oid column_table(size_type col_num) const; + + /// What table did this column come from? + [[nodiscard]] oid column_table(zview col_name) const + { + return column_table(column_number(col_name)); + } + + /// What column number in its table did this result column come from? + /** A meaningful answer can be given only if the column in question comes + * directly from a column in a table. If the column is computed in any + * other way, a logic_error will be thrown. + * + * @param col_num a zero-based column number in this result set + * @return a zero-based column number in originating table + */ + [[nodiscard]] size_type table_column(size_type) const; + + /// What column number in its table did this result column come from? + [[nodiscard]] size_type table_column(zview col_name) const + { + return table_column(column_number(col_name)); + } + //@} + + [[nodiscard]] constexpr result::size_type num() const noexcept + { + return rownumber(); + } + + /** Produce a slice of this row, containing the given range of columns. + * + * @deprecated I haven't heard of anyone caring about row slicing at all in + * at least the last 15 years. Yet it adds complexity, so unless anyone + * files a bug explaining why they really need this feature, I'm going to + * remove it. Even if they do, the feature may need an update. + * + * The slice runs from the range's starting column to the range's end + * column, exclusive. It looks just like a normal result row, except + * slices can be empty. + */ + [[deprecated("Row slicing is going away. File a bug if you need it.")]] row + slice(size_type sbegin, size_type send) const; + + /// Is this a row without fields? Can only happen to a slice. + [[nodiscard, deprecated("Row slicing is going away.")]] PQXX_PURE bool + empty() const noexcept; + + /// Extract entire row's values into a tuple. + /** Converts to the types of the tuple's respective fields. + */ + template void to(Tuple &t) const + { + check_size(std::tuple_size_v); + convert(t); + } + + template std::tuple as() const + { + check_size(sizeof...(TYPE)); + using seq = std::make_index_sequence; + return get_tuple>(seq{}); + } + +protected: + friend class const_row_iterator; + friend class result; + row(result const &r, result_size_type index, size_type cols) noexcept; + + /// Throw @ref usage_error if row size is not `expected`. + void check_size(size_type expected) const + { + if (size() != expected) + throw usage_error{internal::concat( + "Tried to extract ", expected, " field(s) from a row of ", size(), + ".")}; + } + + /// Convert to a given tuple of values, don't check sizes. + /** We need this for cases where we have a full tuple of field types, but + * not a parameter pack. + */ + template TUPLE as_tuple() const + { + using seq = std::make_index_sequence>; + return get_tuple(seq{}); + } + + template friend class pqxx::internal::result_iter; + /// Convert entire row to tuple fields, without checking row size. + template void convert(Tuple &t) const + { + extract_fields(t, std::make_index_sequence>{}); + } + + friend class field; + + /// Result set of which this is one row. + result m_result; + + /// Row number. + /** + * You'd expect this to be unsigned, but due to the way reverse iterators + * are related to regular iterators, it must be allowed to underflow to -1. + */ + result::size_type m_index = 0; + + // TODO: Remove m_begin and (if possible) m_end when we remove slice(). + /// First column in slice. This row ignores lower-numbered columns. + size_type m_begin = 0; + /// End column in slice. This row only sees lower-numbered columns. + size_type m_end = 0; + +private: + template + void extract_fields(Tuple &t, std::index_sequence) const + { + (extract_value(t), ...); + } + + template + void extract_value(Tuple &t) const; + + /// Convert row's values as a new tuple. + template + auto get_tuple(std::index_sequence) const + { + return std::make_tuple(get_field()...); + } + + /// Extract and convert a field. + template auto get_field() const + { + return (*this)[index].as>(); + } +}; + + +/// Iterator for fields in a row. Use as row::const_iterator. +class PQXX_LIBEXPORT const_row_iterator : public field +{ +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = field const; + using pointer = field const *; + using size_type = row_size_type; + using difference_type = row_difference_type; + using reference = field; + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + const_row_iterator() = default; +#include "pqxx/internal/ignore-deprecated-post.hxx" + const_row_iterator(row const &t, row_size_type c) noexcept : + field{t.m_result, t.m_index, c} + {} + const_row_iterator(field const &F) noexcept : field{F} {} + const_row_iterator(const_row_iterator const &) noexcept = default; + const_row_iterator(const_row_iterator &&) noexcept = default; + + /** + * @name Dereferencing operators + */ + //@{ + [[nodiscard]] constexpr pointer operator->() const noexcept { return this; } + [[nodiscard]] reference operator*() const noexcept { return {*this}; } + //@} + + /** + * @name Manipulations + */ + //@{ + const_row_iterator &operator=(const_row_iterator const &) noexcept = default; + const_row_iterator &operator=(const_row_iterator &&) noexcept = default; + + // TODO: noexcept. Breaks ABI. + const_row_iterator operator++(int); + const_row_iterator &operator++() noexcept + { + ++m_col; + return *this; + } + // TODO: noexcept. Breaks ABI. + const_row_iterator operator--(int); + const_row_iterator &operator--() noexcept + { + --m_col; + return *this; + } + + const_row_iterator &operator+=(difference_type i) noexcept + { + m_col = size_type(difference_type(m_col) + i); + return *this; + } + const_row_iterator &operator-=(difference_type i) noexcept + { + m_col = size_type(difference_type(m_col) - i); + return *this; + } + //@} + + /** + * @name Comparisons + */ + //@{ + [[nodiscard]] constexpr bool + operator==(const_row_iterator const &i) const noexcept + { + return col() == i.col(); + } + [[nodiscard]] constexpr bool + operator!=(const_row_iterator const &i) const noexcept + { + return col() != i.col(); + } + [[nodiscard]] constexpr bool + operator<(const_row_iterator const &i) const noexcept + { + return col() < i.col(); + } + [[nodiscard]] constexpr bool + operator<=(const_row_iterator const &i) const noexcept + { + return col() <= i.col(); + } + [[nodiscard]] constexpr bool + operator>(const_row_iterator const &i) const noexcept + { + return col() > i.col(); + } + [[nodiscard]] constexpr bool + operator>=(const_row_iterator const &i) const noexcept + { + return col() >= i.col(); + } + //@} + + /** + * @name Arithmetic operators + */ + //@{ + [[nodiscard]] inline const_row_iterator + operator+(difference_type) const noexcept; + + friend const_row_iterator + operator+(difference_type, const_row_iterator const &) noexcept; + + [[nodiscard]] inline const_row_iterator + operator-(difference_type) const noexcept; + [[nodiscard]] inline difference_type + operator-(const_row_iterator const &) const noexcept; + //@} +}; + + +/// Reverse iterator for a row. Use as row::const_reverse_iterator. +class PQXX_LIBEXPORT const_reverse_row_iterator : private const_row_iterator +{ +public: + using super = const_row_iterator; + using iterator_type = const_row_iterator; + using iterator_type::difference_type; + using iterator_type::iterator_category; + using iterator_type::pointer; + using value_type = iterator_type::value_type; + using reference = iterator_type::reference; + + const_reverse_row_iterator() noexcept = default; + const_reverse_row_iterator(const_reverse_row_iterator const &) noexcept = + default; + const_reverse_row_iterator(const_reverse_row_iterator &&) noexcept = default; + + explicit const_reverse_row_iterator(super const &rhs) noexcept : + const_row_iterator{rhs} + { + super::operator--(); + } + + [[nodiscard]] PQXX_PURE iterator_type base() const noexcept; + + /** + * @name Dereferencing operators + */ + //@{ + using iterator_type::operator->; + using iterator_type::operator*; + //@} + + /** + * @name Manipulations + */ + //@{ + const_reverse_row_iterator & + operator=(const_reverse_row_iterator const &r) noexcept + { + iterator_type::operator=(r); + return *this; + } + const_reverse_row_iterator operator++() noexcept + { + iterator_type::operator--(); + return *this; + } + // TODO: noexcept. Breaks ABI. + const_reverse_row_iterator operator++(int); + const_reverse_row_iterator &operator--() noexcept + { + iterator_type::operator++(); + return *this; + } + const_reverse_row_iterator operator--(int); + // TODO: noexcept. Breaks ABI. + const_reverse_row_iterator &operator+=(difference_type i) noexcept + { + iterator_type::operator-=(i); + return *this; + } + const_reverse_row_iterator &operator-=(difference_type i) noexcept + { + iterator_type::operator+=(i); + return *this; + } + //@} + + /** + * @name Arithmetic operators + */ + //@{ + [[nodiscard]] const_reverse_row_iterator + operator+(difference_type i) const noexcept + { + return const_reverse_row_iterator{base() - i}; + } + [[nodiscard]] const_reverse_row_iterator + operator-(difference_type i) noexcept + { + return const_reverse_row_iterator{base() + i}; + } + [[nodiscard]] difference_type + operator-(const_reverse_row_iterator const &rhs) const noexcept + { + return rhs.const_row_iterator::operator-(*this); + } + //@} + + /** + * @name Comparisons + */ + //@{ + [[nodiscard]] bool + operator==(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator==(rhs); + } + [[nodiscard]] bool + operator!=(const_reverse_row_iterator const &rhs) const noexcept + { + return !operator==(rhs); + } + + [[nodiscard]] constexpr bool + operator<(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator>(rhs); + } + [[nodiscard]] constexpr bool + operator<=(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator>=(rhs); + } + [[nodiscard]] constexpr bool + operator>(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator<(rhs); + } + [[nodiscard]] constexpr bool + operator>=(const_reverse_row_iterator const &rhs) const noexcept + { + return iterator_type::operator<=(rhs); + } + //@} +}; + + +const_row_iterator +const_row_iterator::operator+(difference_type o) const noexcept +{ + // TODO:: More direct route to home().columns()? + return { + row{home(), idx(), home().columns()}, + size_type(difference_type(col()) + o)}; +} + +inline const_row_iterator operator+( + const_row_iterator::difference_type o, const_row_iterator const &i) noexcept +{ + return i + o; +} + +inline const_row_iterator +const_row_iterator::operator-(difference_type o) const noexcept +{ + // TODO:: More direct route to home().columns()? + return { + row{home(), idx(), home().columns()}, + size_type(difference_type(col()) - o)}; +} + +inline const_row_iterator::difference_type +const_row_iterator::operator-(const_row_iterator const &i) const noexcept +{ + return difference_type(num() - i.num()); +} + + +template +inline void row::extract_value(Tuple &t) const +{ + using field_type = strip_t(t))>; + field const f{m_result, m_index, index}; + std::get(t) = from_string(f); +} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list new file mode 100644 index 000000000..1bdf51c6a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list @@ -0,0 +1,6 @@ +/** Helper similar to Python's @c str.join(). + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/separated_list.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list.hxx new file mode 100644 index 000000000..d4230ea08 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/separated_list.hxx @@ -0,0 +1,142 @@ +/* Helper similar to Python's `str.join()`. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/separated_list instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_SEPARATED_LIST +#define PQXX_H_SEPARATED_LIST + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +#include "pqxx/strconv.hxx" + +// C++20: Simplify using std::ranges::range. +// C++20: Optimise buffer allocation using random_access_range/iterator. +namespace pqxx +{ +/** + * @defgroup utility Utility functions + */ +//@{ + +/// Represent sequence of values as a string, joined by a given separator. +/** + * Use this to turn e.g. the numbers 1, 2, and 3 into a string "1, 2, 3". + * + * @param sep separator string (to be placed between items) + * @param begin beginning of items sequence + * @param end end of items sequence + * @param access functor defining how to dereference sequence elements + */ +template +[[nodiscard]] inline std::string +separated_list(std::string_view sep, ITER begin, ITER end, ACCESS access) +{ + if (end == begin) + return {}; + auto next{begin}; + ++next; + if (next == end) + return to_string(access(begin)); + + // From here on, we've got at least 2 elements -- meaning that we need sep. + using elt_type = strip_t; + using traits = string_traits; + + std::size_t budget{0}; + for (ITER cnt{begin}; cnt != end; ++cnt) + budget += traits::size_buffer(access(cnt)); + budget += + static_cast(std::distance(begin, end)) * std::size(sep); + + std::string result; + result.resize(budget); + + char *const data{result.data()}; + char *here{data}; + char *stop{data + budget}; + here = traits::into_buf(here, stop, access(begin)) - 1; + for (++begin; begin != end; ++begin) + { + here += sep.copy(here, std::size(sep)); + here = traits::into_buf(here, stop, access(begin)) - 1; + } + result.resize(static_cast(here - data)); + return result; +} + + +/// Render sequence as a string, using given separator between items. +template +[[nodiscard]] inline std::string +separated_list(std::string_view sep, ITER begin, ITER end) +{ + return separated_list(sep, begin, end, [](ITER i) { return *i; }); +} + + +/// Render items in a container as a string, using given separator. +template +[[nodiscard]] inline auto +separated_list(std::string_view sep, CONTAINER const &c) + /* + Always std::string; necessary because SFINAE doesn't work with the + contents of function bodies, so the check for iterability has to be in + the signature. + */ + -> typename std::enable_if< + (not std::is_void::value and + not std::is_void::value), + std::string>::type +{ + return separated_list(sep, std::begin(c), std::end(c)); +} + + +/// Render items in a tuple as a string, using given separator. +template< + typename TUPLE, std::size_t INDEX = 0, typename ACCESS, + typename std::enable_if< + (INDEX == std::tuple_size::value - 1), int>::type = 0> +[[nodiscard]] inline std::string separated_list( + std::string_view /* sep */, TUPLE const &t, ACCESS const &access) +{ + return to_string(access(&std::get(t))); +} + +template< + typename TUPLE, std::size_t INDEX = 0, typename ACCESS, + typename std::enable_if< + (INDEX < std::tuple_size::value - 1), int>::type = 0> +[[nodiscard]] inline std::string +separated_list(std::string_view sep, TUPLE const &t, ACCESS const &access) +{ + std::string out{to_string(access(&std::get(t)))}; + out.append(sep); + out.append(separated_list(sep, t, access)); + return out; +} + +template< + typename TUPLE, std::size_t INDEX = 0, + typename std::enable_if< + (INDEX <= std::tuple_size::value), int>::type = 0> +[[nodiscard]] inline std::string +separated_list(std::string_view sep, TUPLE const &t) +{ + // TODO: Optimise allocation. + return separated_list(sep, t, [](TUPLE const &tup) { return *tup; }); +} +//@} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv new file mode 100644 index 000000000..aa2c40ed5 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv @@ -0,0 +1,6 @@ +/** String conversion definitions. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/strconv.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv.hxx new file mode 100644 index 000000000..863711228 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/strconv.hxx @@ -0,0 +1,468 @@ +/* String conversion definitions. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stringconv instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STRCONV +#define PQXX_H_STRCONV + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#endif + +#if defined(PQXX_HAVE_RANGES) && __has_include() +# include +#endif + +#include "pqxx/except.hxx" +#include "pqxx/util.hxx" +#include "pqxx/zview.hxx" + + +namespace pqxx::internal +{ +/// Attempt to demangle @c std::type_info::name() to something human-readable. +PQXX_LIBEXPORT std::string demangle_type_name(char const[]); +} // namespace pqxx::internal + + +namespace pqxx +{ +/** + * @defgroup stringconversion String conversion + * + * The PostgreSQL server accepts and represents data in string form. It has + * its own formats for various data types. The string conversions define how + * various C++ types translate to and from their respective PostgreSQL text + * representations. + * + * Each conversion is defined by a specialisations of @c string_traits. It + * gets complicated if you want top performance, but until you do, all you + * really need to care about when converting values between C++ in-memory + * representations such as @c int and the postgres string representations is + * the @c pqxx::to_string and @c pqxx::from_string functions. + * + * If you need to convert a type which is not supported out of the box, you'll + * need to define your own specialisations for these templates, similar to the + * ones defined here and in `pqxx/conversions.hxx`. Any conversion code which + * "sees" your specialisation will now support your conversion. In particular, + * you'll be able to read result fields into a variable of the new type. + * + * There is a macro to help you define conversions for individual enumeration + * types. The conversion will represent enumeration values as numeric strings. + */ +//@{ + +/// A human-readable name for a type, used in error messages and such. +/** Actually this may not always be very user-friendly. It uses + * @c std::type_info::name(). On gcc-like compilers we try to demangle its + * output. Visual Studio produces human-friendly names out of the box. + * + * This variable is not inline. Inlining it gives rise to "memory leak" + * warnings from asan, the address sanitizer, possibly from use of + * @c std::type_info::name. + */ +template +std::string const type_name{internal::demangle_type_name(typeid(TYPE).name())}; + + +/// Traits describing a type's "null value," if any. +/** Some C++ types have a special value or state which correspond directly to + * SQL's NULL. + * + * The @c nullness traits describe whether it exists, and whether a particular + * value is null. + */ +template struct nullness +{ + /// Does this type have a null value? + static bool has_null; + + /// Is this type always null? + static bool always_null; + + /// Is @c value a null? + static bool is_null(TYPE const &value); + + /// Return a null value. + /** Don't use this in generic code to compare a value and see whether it is + * null. Some types may have multiple null values which do not compare as + * equal, or may define a null value which is not equal to anything including + * itself, like in SQL. + */ + [[nodiscard]] static TYPE null(); +}; + + +/// Nullness traits describing a type which does not have a null value. +template struct no_null +{ + /// Does @c TYPE have a "built-in null value"? + /** For example, a pointer can equal @c nullptr, which makes a very natural + * representation of an SQL null value. For such types, the code sometimes + * needs to make special allowances. + * + * for most types, such as @c int or @c std::string, there is no built-in + * null. If you want to represent an SQL null value for such a type, you + * would have to wrap it in something that does have a null value. For + * example, you could use @c std::optional for "either an @c int or a + * null value." + */ + static constexpr bool has_null = false; + + /// Are all values of this type null? + /** There are a few special C++ types which are always null - mainly + * @c std::nullptr_t. + */ + static constexpr bool always_null = false; + + /// Does a given value correspond to an SQL null value? + /** Most C++ types, such as @c int or @c std::string, have no inherent null + * value. But some types such as C-style string pointers do have a natural + * equivalent to an SQL null. + */ + [[nodiscard]] static constexpr bool is_null(TYPE const &) noexcept + { + return false; + } +}; + + +/// Traits class for use in string conversions. +/** Specialize this template for a type for which you wish to add to_string + * and from_string support. + * + * String conversions are not meant to work for nulls. Check for null before + * converting a value of @c TYPE to a string, or vice versa. + */ +template struct string_traits +{ + /// Return a @c string_view representing value, plus terminating zero. + /** Produces a @c string_view containing the PostgreSQL string representation + * for @c value. + * + * Uses the space from @c begin to @c end as a buffer, if needed. The + * returned string may lie somewhere in that buffer, or it may be a + * compile-time constant, or it may be null if value was a null value. Even + * if the string is stored in the buffer, its @c begin() may or may not be + * the same as @c begin. + * + * The @c string_view is guaranteed to be valid as long as the buffer from + * @c begin to @c end remains accessible and unmodified. + * + * @throws pqxx::conversion_overrun if the provided buffer space may not be + * enough. For maximum performance, this is a conservative estimate. It may + * complain about a buffer which is actually large enough for your value, if + * an exact check gets too expensive. + */ + [[nodiscard]] static inline zview + to_buf(char *begin, char *end, TYPE const &value); + + /// Write value's string representation into buffer at @c begin. + /** Assumes that value is non-null. + * + * Writes value's string representation into the buffer, starting exactly at + * @c begin, and ensuring a trailing zero. Returns the address just beyond + * the trailing zero, so the caller could use it as the @c begin for another + * call to @c into_buf writing a next value. + */ + static inline char *into_buf(char *begin, char *end, TYPE const &value); + + /// Parse a string representation of a @c TYPE value. + /** Throws @c conversion_error if @c value does not meet the expected format + * for a value of this type. + */ + [[nodiscard]] static inline TYPE from_string(std::string_view text); + + // C++20: Can we make these all constexpr? + /// Estimate how much buffer space is needed to represent value. + /** The estimate may be a little pessimistic, if it saves time. + * + * The estimate includes the terminating zero. + */ + [[nodiscard]] static inline std::size_t + size_buffer(TYPE const &value) noexcept; +}; + + +/// Nullness: Enums do not have an inherent null value. +template +struct nullness>> : no_null +{}; +} // namespace pqxx + + +namespace pqxx::internal +{ +/// Helper class for defining enum conversions. +/** The conversion will convert enum values to numeric strings, and vice versa. + * + * To define a string conversion for an enum type, derive a @c string_traits + * specialisation for the enum from this struct. + * + * There's usually an easier way though: the @c PQXX_DECLARE_ENUM_CONVERSION + * macro. Use @c enum_traits manually only if you need to customise your + * traits type in more detail. + */ +template struct enum_traits +{ + using impl_type = std::underlying_type_t; + using impl_traits = string_traits; + + [[nodiscard]] static constexpr zview + to_buf(char *begin, char *end, ENUM const &value) + { + return impl_traits::to_buf(begin, end, to_underlying(value)); + } + + static constexpr char *into_buf(char *begin, char *end, ENUM const &value) + { + return impl_traits::into_buf(begin, end, to_underlying(value)); + } + + [[nodiscard]] static ENUM from_string(std::string_view text) + { + return static_cast(impl_traits::from_string(text)); + } + + [[nodiscard]] static std::size_t size_buffer(ENUM const &value) noexcept + { + return impl_traits::size_buffer(to_underlying(value)); + } + +private: + // C++23: Replace with std::to_underlying. + static constexpr impl_type to_underlying(ENUM const &value) noexcept + { + return static_cast(value); + } +}; +} // namespace pqxx::internal + + +/// Macro: Define a string conversion for an enum type. +/** This specialises the @c pqxx::string_traits template, so use it in the + * @c ::pqxx namespace. + * + * For example: + * + * #include + * #include + * enum X { xa, xb }; + * namespace pqxx { PQXX_DECLARE_ENUM_CONVERSION(x); } + * int main() { std::cout << pqxx::to_string(xa) << std::endl; } + */ +#define PQXX_DECLARE_ENUM_CONVERSION(ENUM) \ + template<> struct string_traits : pqxx::internal::enum_traits \ + {}; \ + template<> inline std::string const type_name { #ENUM } + + +namespace pqxx +{ +/// Parse a value in postgres' text format as a TYPE. +/** If the form of the value found in the string does not match the expected + * type, e.g. if a decimal point is found when converting to an integer type, + * the conversion fails. Overflows (e.g. converting "9999999999" to a 16-bit + * C++ type) are also treated as errors. If in some cases this behaviour + * should be inappropriate, convert to something bigger such as @c long @c int + * first and then truncate the resulting value. + * + * Only the simplest possible conversions are supported. Fancy features like + * hexadecimal or octal, spurious signs, or exponent notation won't work. + * Whitespace is not stripped away. Only the kinds of strings that come out of + * PostgreSQL and out of to_string() can be converted. + */ +template +[[nodiscard]] inline TYPE from_string(std::string_view text) +{ + return string_traits::from_string(text); +} + + +/// "Convert" a std::string_view to a std::string_view. +/** Just returns its input. + * + * @warning Of course the result is only valid for as long as the original + * string remains valid! Never access the string referenced by the return + * value after the original has been destroyed. + */ +template<> +[[nodiscard]] inline std::string_view from_string(std::string_view text) +{ + return text; +} + + +/// Attempt to convert postgres-generated string to given built-in object. +/** This is like the single-argument form of the function, except instead of + * returning the value, it sets @c value. + * + * You may find this more convenient in that it infers the type you want from + * the argument you pass. But there are disadvantages: it requires an + * assignment operator, and it may be less efficient. + */ +template inline void from_string(std::string_view text, T &value) +{ + value = from_string(text); +} + + +/// Convert a value to a readable string that PostgreSQL will understand. +/** The conversion does no special formatting, and ignores any locale settings. + * The resulting string will be human-readable and in a format suitable for use + * in SQL queries. It won't have niceties such as "thousands separators" + * though. + */ +template inline std::string to_string(TYPE const &value); + + +/// Convert multiple values to strings inside a single buffer. +/** There must be enough room for all values, or this will throw + * @c conversion_overrun. You can obtain a conservative estimate of the buffer + * space required by calling @c size_buffer() on the values. + * + * The @c std::string_view results may point into the buffer, so don't assume + * that they will remain valid after you destruct or move the buffer. + */ +template +[[nodiscard]] inline std::vector +to_buf(char *here, char const *end, TYPE... value) +{ + return {[&here, end](auto v) { + auto begin = here; + here = string_traits::into_buf(begin, end, v); + // Exclude the trailing zero out of the string_view. + auto len{static_cast(here - begin) - 1}; + return std::string_view{begin, len}; + }(value)...}; +} + +/// Convert a value to a readable string that PostgreSQL will understand. +/** This variant of to_string can sometimes save a bit of time in loops, by + * re-using a std::string for multiple conversions. + */ +template +inline void into_string(TYPE const &value, std::string &out); + + +/// Is @c value null? +template +[[nodiscard]] inline constexpr bool is_null(TYPE const &value) noexcept +{ + return nullness>::is_null(value); +} + + +/// Estimate how much buffer space is needed to represent values as a string. +/** The estimate may be a little pessimistic, if it saves time. It also + * includes room for a terminating zero after each value. + */ +template +[[nodiscard]] inline std::size_t size_buffer(TYPE const &...value) noexcept +{ + return (string_traits>::size_buffer(value) + ...); +} + + +/// Does this type translate to an SQL array? +/** Specialisations may override this to be true for container types. + * + * This may not always be a black-and-white choice. For instance, a + * @c std::string is a container, but normally it translates to an SQL string, + * not an SQL array. + */ +template inline constexpr bool is_sql_array{false}; + + +/// Can we use this type in arrays and composite types without quoting them? +/** Define this as @c true only if values of @c TYPE can never contain any + * special characters that might need escaping or confuse the parsing of array + * or composite * types, such as commas, quotes, parentheses, braces, newlines, + * and so on. + * + * When converting a value of such a type to a string in an array or a field in + * a composite type, we do not need to add quotes, nor escape any special + * characters. + * + * This is just an optimisation, so it defaults to @c false to err on the side + * of slow correctness. + */ +template inline constexpr bool is_unquoted_safe{false}; + + +/// Element separator between SQL array elements of this type. +template inline constexpr char array_separator{','}; + + +/// What's the preferred format for passing non-null parameters of this type? +/** This affects how we pass parameters of @c TYPE when calling parameterised + * statements or prepared statements. + * + * Generally we pass parameters in text format, but binary strings are the + * exception. We also pass nulls in binary format, so this function need not + * handle null values. + */ +template inline constexpr format param_format(TYPE const &) +{ + return format::text; +} + + +/// Implement @c string_traits::to_buf by calling @c into_buf. +/** When you specialise @c string_traits for a new type, most of the time its + * @c to_buf implementation has no special optimisation tricks and just writes + * its text into the buffer it receives from the caller, starting at the + * beginning. + * + * In that common situation, you can implement @c to_buf as just a call to + * @c generic_to_buf. It will call @c into_buf and return the right result for + * @c to_buf. + */ +template +inline zview generic_to_buf(char *begin, char *end, TYPE const &value) +{ + using traits = string_traits; + // The trailing zero does not count towards the zview's size, so subtract 1 + // from the result we get from into_buf(). + if (is_null(value)) + return {}; + else + return {begin, traits::into_buf(begin, end, value) - begin - 1}; +} + + +#if defined(PQXX_HAVE_CONCEPTS) +/// Concept: Binary string, akin to @c std::string for binary data. +/** Any type that satisfies this concept can represent an SQL BYTEA value. + * + * A @c binary has a @c begin(), @c end(), @c size(), and @data(). Each byte + * is a @c std::byte, and they must all be laid out contiguously in memory so + * we can reference them by a pointer. + */ +template +concept binary = std::ranges::contiguous_range and + std::is_same_v>, std::byte>; +#endif +//@} +} // namespace pqxx + + +#include "pqxx/internal/conversions.hxx" +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from new file mode 100644 index 000000000..972762443 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from @@ -0,0 +1,8 @@ +/** pqxx::stream_from class. + * + * pqxx::stream_from enables optimized batch reads from a database table. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/stream_from.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from.hxx new file mode 100644 index 000000000..ff4a93d2e --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_from.hxx @@ -0,0 +1,361 @@ +/* Definition of the pqxx::stream_from class. + * + * pqxx::stream_from enables optimized batch reads from a database table. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_from instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STREAM_FROM +#define PQXX_H_STREAM_FROM + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#include "pqxx/connection.hxx" +#include "pqxx/except.hxx" +#include "pqxx/internal/concat.hxx" +#include "pqxx/internal/encoding_group.hxx" +#include "pqxx/internal/stream_iterator.hxx" +#include "pqxx/separated_list.hxx" +#include "pqxx/transaction_focus.hxx" + + +namespace pqxx +{ +class transaction_base; + + +/// Pass this to a `stream_from` constructor to stream table contents. +/** @deprecated Use @ref stream_from::table() instead. + */ +constexpr from_table_t from_table; +/// Pass this to a `stream_from` constructor to stream query results. +/** @deprecated Use stream_from::query() instead. + */ +constexpr from_query_t from_query; + + +/// Stream data from the database. +/** For larger data sets, retrieving data this way is likely to be faster than + * executing a query and then iterating and converting the rows fields. You + * will also be able to start processing before all of the data has come in. + * + * There are also downsides. Not all kinds of query will work in a stream. + * But straightforward `SELECT` and `UPDATE ... RETURNING` queries should work. + * This function makes use of @ref pqxx::stream_from, which in turn uses + * PostgreSQL's `COPY` command, so see the documentation for those to get the + * full details. + * + * There are other downsides. If there stream encounters an error, it may + * leave the entire connection in an unusable state, so you'll have to give the + * whole thing up. Finally, opening a stream puts the connection in a special + * state, so you won't be able to do many other things with the connection or + * the transaction while the stream is open. + * + * There are two ways of starting a stream: you stream either all rows in a + * table (using one of the factories, `table()` or `raw_table()`), or the + * results of a query (using the `query()` factory). + * + * Usually you'll want the `stream` convenience wrapper in + * @ref transaction_base, * so you don't need to deal with this class directly. + * + * @warning While a stream is active, you cannot execute queries, open a + * pipeline, etc. on the same transaction. A transaction can have at most one + * object of a type derived from @ref pqxx::transaction_focus active on it at a + * time. + */ +class PQXX_LIBEXPORT stream_from : transaction_focus +{ +public: + using raw_line = + std::pair>, std::size_t>; + + /// Factory: Execute query, and stream the results. + /** The query can be a SELECT query or a VALUES query; or it can be an + * UPDATE, INSERT, or DELETE with a RETURNING clause. + * + * The query is executed as part of a COPY statement, so there are additional + * restrictions on what kind of query you can use here. See the PostgreSQL + * documentation for the COPY command: + * + * https://www.postgresql.org/docs/current/sql-copy.html + */ + static stream_from query(transaction_base &tx, std::string_view q) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return {tx, from_query, q}; +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /** + * @name Streaming data from tables + * + * You can use `stream_from` to read a table's contents. This is a quick + * and easy way to read a table, but it comes with limitations. It cannot + * stream from a view, only from a table. It does not support conditions. + * And there are no guarantees about ordering. If you need any of those + * things, consider streaming from a query instead. + */ + //@{ + + /// Factory: Stream data from a pre-quoted table and columns. + /** Use this factory if you need to create multiple streams using the same + * table path and/or columns list, and you want to save a bit of work on + * composing the internal SQL statement for starting the stream. It lets you + * compose the string representations for the table path and the columns + * list, so you can compute these once and then re-use them later. + * + * @param tx The transaction within which the stream will operate. + * @param path Name or path for the table upon which the stream will + * operate. If any part of the table path may contain special + * characters or be case-sensitive, quote the path using + * pqxx::connection::quote_table(). + * @param columns Columns which the stream will read. They should be + * comma-separated and, if needed, quoted. You can produce the string + * using pqxx::connection::quote_columns(). If you omit this argument, + * the stream will read all columns in the table, in schema order. + */ + static stream_from raw_table( + transaction_base &tx, std::string_view path, + std::string_view columns = ""sv); + + /// Factory: Stream data from a given table. + /** This is the convenient way to stream from a table. + */ + static stream_from table( + transaction_base &tx, table_path path, + std::initializer_list columns = {}); + //@} + + /// Execute query, and stream over the results. + /** @deprecated Use factory function @ref query instead. + */ + [[deprecated("Use query() factory instead.")]] stream_from( + transaction_base &, from_query_t, std::string_view query); + + /// Stream all rows in table, all columns. + /** @deprecated Use factories @ref table or @ref raw_table instead. + */ + [[deprecated("Use table() or raw_table() factory instead.")]] stream_from( + transaction_base &, from_table_t, std::string_view table); + + /// Stream given columns from all rows in table. + /** @deprecated Use factories @ref table or @ref raw_table instead. + */ + template + [[deprecated("Use table() or raw_table() factory instead.")]] stream_from( + transaction_base &, from_table_t, std::string_view table, + Iter columns_begin, Iter columns_end); + + /// Stream given columns from all rows in table. + /** @deprecated Use factory function @ref query instead. + */ + template + [[deprecated("Use table() or raw_table() factory instead.")]] stream_from( + transaction_base &tx, from_table_t, std::string_view table, + Columns const &columns); + +#include "pqxx/internal/ignore-deprecated-pre.hxx" + /// @deprecated Use factories @ref table or @ref raw_table instead. + [[deprecated("Use the from_table_t overload instead.")]] stream_from( + transaction_base &tx, std::string_view table) : + stream_from{tx, from_table, table} + {} +#include "pqxx/internal/ignore-deprecated-post.hxx" + + /// @deprecated Use factories @ref table or @ref raw_table instead. + template + [[deprecated("Use the from_table_t overload instead.")]] stream_from( + transaction_base &tx, std::string_view table, Columns const &columns) : + stream_from{tx, from_table, table, columns} + {} + + /// @deprecated Use factories @ref table or @ref raw_table instead. + template + [[deprecated("Use the from_table_t overload instead.")]] stream_from( + transaction_base &, std::string_view table, Iter columns_begin, + Iter columns_end); + + ~stream_from() noexcept; + + /// May this stream still produce more data? + [[nodiscard]] constexpr operator bool() const noexcept + { + return not m_finished; + } + /// Has this stream produced all the data it is going to produce? + [[nodiscard]] constexpr bool operator!() const noexcept + { + return m_finished; + } + + /// Finish this stream. Call this before continuing to use the connection. + /** Consumes all remaining lines, and closes the stream. + * + * This may take a while if you're abandoning the stream before it's done, so + * skip it in error scenarios where you're not planning to use the connection + * again afterwards. + */ + void complete(); + + /// Read one row into a tuple. + /** Converts the row's fields into the fields making up the tuple. + * + * For a column which can contain nulls, be sure to give the corresponding + * tuple field a type which can be null. For example, to read a field as + * `int` when it may contain nulls, read it as `std::optional`. + * Using `std::shared_ptr` or `std::unique_ptr` will also work. + */ + template stream_from &operator>>(Tuple &); + + /// Doing this with a `std::variant` is going to be horrifically borked. + template + stream_from &operator>>(std::variant &) = delete; + + /// Iterate over this stream. Supports range-based "for" loops. + /** Produces an input iterator over the stream. + * + * Do not call this yourself. Use it like "for (auto data : stream.iter())". + */ + template [[nodiscard]] auto iter() & + { + return pqxx::internal::stream_input_iteration{*this}; + } + + /// Read a row. Return fields as views, valid until you read the next row. + /** Returns `nullptr` when there are no more rows to read. Do not attempt + * to read any further rows after that. + * + * Do not access the vector, or the storage referenced by the views, after + * closing or completing the stream, or after attempting to read a next row. + * + * A @ref pqxx::zview is like a `std::string_view`, but with the added + * guarantee that if its data pointer is non-null, the string is followed by + * a terminating zero (which falls just outside the view itself). + * + * If any of the views' data pointer is null, that means that the + * corresponding SQL field is null. + * + * @warning The return type may change in the future, to support C++20 + * coroutine-based usage. + */ + std::vector const *read_row() &; + + /// Read a raw line of text from the COPY command. + /** @warning Do not use this unless you really know what you're doing. */ + raw_line get_raw_line(); + +private: + // TODO: Clean up this signature once we cull the deprecated constructors. + /// @deprecated + stream_from( + transaction_base &tx, std::string_view table, std::string_view columns, + from_table_t); + + // TODO: Clean up this signature once we cull the deprecated constructors. + /// @deprecated + stream_from( + transaction_base &, std::string_view unquoted_table, + std::string_view columns, from_table_t, int); + + template + void extract_fields(Tuple &t, std::index_sequence) const + { + (extract_value(t), ...); + } + + pqxx::internal::glyph_scanner_func *m_glyph_scanner; + + /// Current row's fields' text, combined into one reusable string. + std::string m_row; + + /// The current row's fields. + std::vector m_fields; + + bool m_finished = false; + + void close(); + + template + void extract_value(Tuple &) const; + + /// Read a line of COPY data, write `m_row` and `m_fields`. + void parse_line(); +}; + + +template +inline stream_from::stream_from( + transaction_base &tx, from_table_t, std::string_view table_name, + Columns const &columns) : + stream_from{ + tx, from_table, table_name, std::begin(columns), std::end(columns)} +{} + + +template +inline stream_from::stream_from( + transaction_base &tx, from_table_t, std::string_view table, + Iter columns_begin, Iter columns_end) : + stream_from{ + tx, table, separated_list(",", columns_begin, columns_end), + from_table, 1} +{} + + +template inline stream_from &stream_from::operator>>(Tuple &t) +{ + if (m_finished) + return *this; + static constexpr auto tup_size{std::tuple_size_v}; + m_fields.reserve(tup_size); + parse_line(); + if (m_finished) + return *this; + + if (std::size(m_fields) != tup_size) + throw usage_error{internal::concat( + "Tried to extract ", tup_size, " field(s) from a stream of ", + std::size(m_fields), ".")}; + + extract_fields(t, std::make_index_sequence{}); + return *this; +} + + +template +inline void stream_from::extract_value(Tuple &t) const +{ + using field_type = strip_t(t))>; + using nullity = nullness; + assert(index < std::size(m_fields)); + if constexpr (nullity::always_null) + { + if (std::data(m_fields[index]) != nullptr) + throw conversion_error{"Streaming non-null value into null field."}; + } + else if (std::data(m_fields[index]) == nullptr) + { + if constexpr (nullity::has_null) + std::get(t) = nullity::null(); + else + internal::throw_null_conversion(type_name); + } + else + { + // Don't ever try to convert a non-null value to nullptr_t! + std::get(t) = from_string(m_fields[index]); + } +} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to new file mode 100644 index 000000000..8760cf1f4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to @@ -0,0 +1,8 @@ +/** pqxx::stream_to class. + * + * pqxx::stream_to enables optimized batch updates to a database table. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/stream_to.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to.hxx new file mode 100644 index 000000000..2a49d8f85 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/stream_to.hxx @@ -0,0 +1,455 @@ +/* Definition of the pqxx::stream_to class. + * + * pqxx::stream_to enables optimized batch updates to a database table. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_to.hxx instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_STREAM_TO +#define PQXX_H_STREAM_TO + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/separated_list.hxx" +#include "pqxx/transaction_base.hxx" + + +namespace pqxx +{ +/// Efficiently write data directly to a database table. +/** If you wish to insert rows of data into a table, you can compose INSERT + * statements and execute them. But it's slow and tedious, and you need to + * worry about quoting and escaping the data. + * + * If you're just inserting a single row, it probably won't matter much. You + * can use prepared or parameterised statements to take care of the escaping + * for you. But if you're inserting large numbers of rows you will want + * something better. + * + * Inserting rows one by one using INSERT statements involves a lot of + * pointless overhead, especially when you are working with a remote database + * server over the network. You may end up sending each row over the network + * as a separate query, and waiting for a reply. Do it "in bulk" using + * `stream_to`, and you may find that it goes many times faster. Sometimes + * you gain orders of magnitude in speed. + * + * Here's how it works: you create a `stream_to` stream to start writing to + * your table. You will probably want to specify the columns. Then, you + * feed your data into the stream one row at a time. And finally, you call the + * stream's @ref complete function to tell it to finalise the operation, wait + * for completion, and check for errors. + * + * (You _must_ complete the stream before committing or aborting the + * transaction. The connection is in a special state while the stream is + * active, where it can't process commands, and can't commit or abort a + * transaction.) + * + * So how do you feed a row of data into the stream? There's several ways, but + * the preferred one is to call its @ref write_values. Pass the field values + * as arguments. Doesn't matter what type they are, as long as libpqxx knows + * how to convert them to PostgreSQL's text format: `int`, `std::string` or + * `std:string_view`, `float` and `double`, `bool`... lots of basic types + * are supported. If some of the values are null, feel free to use + * `std::optional`, `std::shared_ptr`, or `std::unique_ptr`. + * + * The arguments' types don't even have to match the fields' SQL types. If you + * want to insert an `int` into a `DECIMAL` column, that's your choice -- it + * will produce a `DECIMAL` value which happens to be integral. Insert a + * `float` into a `VARCHAR` column? That's fine, you'll get a string whose + * contents happen to read like a number. And so on. You can even insert + * different types of value in the same column on different rows. If you have + * a code path where a particular field is always null, just insert `nullptr`. + * + * There is another way to insert rows: the `<<` ("shift-left") operator. + * It's not as fast and it doesn't support variable arguments: each row must be + * either a `std::tuple` or something iterable, such as a `std::vector`, or + * anything else with a `begin()` and `end()`. + * + * @warning While a stream is active, you cannot execute queries, open a + * pipeline, etc. on the same transaction. A transaction can have at most one + * object of a type derived from @ref pqxx::transaction_focus active on it at a + * time. + */ +class PQXX_LIBEXPORT stream_to : transaction_focus +{ +public: + /// Stream data to a pre-quoted table and columns. + /** This factory can be useful when it's not convenient to provide the + * columns list in the form of a `std::initializer_list`, or when the list + * of columns is simply not known at compile time. + * + * Also use this if you need to create multiple streams using the same table + * path and/or columns list, and you want to save a bit of work on composing + * the internal SQL statement for starting the stream. It lets you compose + * the string representations for the table path and the columns list, so you + * can compute these once and then re-use them later. + * + * @param tx The transaction within which the stream will operate. + * @param path Name or path for the table upon which the stream will + * operate. If any part of the table path may contain special + * characters or be case-sensitive, quote the path using + * pqxx::connection::quote_table(). + * @param columns Columns to which the stream will write. They should be + * comma-separated and, if needed, quoted. You can produce the string + * using pqxx::connection::quote_columns(). If you omit this argument, + * the stream will write all columns in the table, in schema order. + */ + static stream_to raw_table( + transaction_base &tx, std::string_view path, std::string_view columns = "") + { + return {tx, path, columns}; + } + + /// Create a `stream_to` writing to a named table and columns. + /** Use this to stream data to a table, where the list of columns is known at + * compile time. + * + * @param tx The transaction within which the stream will operate. + * @param path A @ref table_path designating the target table. + * @param columns Optionally, the columns to which the stream should write. + * If you do not pass this, the stream will write to all columns in the + * table, in schema order. + */ + static stream_to table( + transaction_base &tx, table_path path, + std::initializer_list columns = {}) + { + auto const &conn{tx.conn()}; + return raw_table(tx, conn.quote_table(path), conn.quote_columns(columns)); + } + +#if defined(PQXX_HAVE_CONCEPTS) + /// Create a `stream_to` writing to a named table and columns. + /** Use this version to stream data to a table, when the list of columns is + * not known at compile time. + * + * @param tx The transaction within which the stream will operate. + * @param path A @ref table_path designating the target table. + * @param columns The columns to which the stream should write. + */ + template + static stream_to + table(transaction_base &tx, table_path path, COLUMNS const &columns) + { + auto const &conn{tx.conn()}; + return stream_to::raw_table( + tx, conn.quote_table(path), tx.conn().quote_columns(columns)); + } + + /// Create a `stream_to` writing to a named table and columns. + /** Use this version to stream data to a table, when the list of columns is + * not known at compile time. + * + * @param tx The transaction within which the stream will operate. + * @param path A @ref table_path designating the target table. + * @param columns The columns to which the stream should write. + */ + template + static stream_to + table(transaction_base &tx, std::string_view path, COLUMNS const &columns) + { + return stream_to::raw_table(tx, path, tx.conn().quote_columns(columns)); + } +#endif // PQXX_HAVE_CONCEPTS + + /// Create a stream, without specifying columns. + /** @deprecated Use @ref table or @ref raw_table as a factory. + * + * Fields will be inserted in whatever order the columns have in the + * database. + * + * You'll probably want to specify the columns, so that the mapping between + * your data fields and the table is explicit in your code, and not hidden + * in an "implicit contract" between your code and your schema. + */ + [[deprecated("Use table() or raw_table() factory.")]] stream_to( + transaction_base &tx, std::string_view table_name) : + stream_to{tx, table_name, ""sv} + {} + + /// Create a stream, specifying column names as a container of strings. + /** @deprecated Use @ref table or @ref raw_table as a factory. + */ + template + [[deprecated("Use table() or raw_table() factory.")]] stream_to( + transaction_base &, std::string_view table_name, Columns const &columns); + + /// Create a stream, specifying column names as a sequence of strings. + /** @deprecated Use @ref table or @ref raw_table as a factory. + */ + template + [[deprecated("Use table() or raw_table() factory.")]] stream_to( + transaction_base &, std::string_view table_name, Iter columns_begin, + Iter columns_end); + + ~stream_to() noexcept; + + /// Does this stream still need to @ref complete()? + [[nodiscard]] constexpr operator bool() const noexcept + { + return not m_finished; + } + /// Has this stream been through its concluding @c complete()? + [[nodiscard]] constexpr bool operator!() const noexcept + { + return m_finished; + } + + /// Complete the operation, and check for errors. + /** Always call this to close the stream in an orderly fashion, even after + * an error. (In the case of an error, abort the transaction afterwards.) + * + * The only circumstance where it's safe to skip this is after an error, if + * you're discarding the entire connection. + */ + void complete(); + + /// Insert a row of data. + /** Returns a reference to the stream, so you can chain the calls. + * + * The @c row can be a tuple, or any type that can be iterated. Each + * item becomes a field in the row, in the same order as the columns you + * specified when creating the stream. + * + * If you don't already happen to have your fields in the form of a tuple or + * container, prefer @c write_values. It's faster and more convenient. + */ + template stream_to &operator<<(Row const &row) + { + write_row(row); + return *this; + } + + /// Stream a `stream_from` straight into a `stream_to`. + /** This can be useful when copying between different databases. If the + * source and the destination are on the same database, you'll get better + * performance doing it all in a regular query. + */ + stream_to &operator<<(stream_from &); + + /// Insert a row of data, given in the form of a @c std::tuple or container. + /** The @c row can be a tuple, or any type that can be iterated. Each + * item becomes a field in the row, in the same order as the columns you + * specified when creating the stream. + * + * The preferred way to insert a row is @c write_values. + */ + template void write_row(Row const &row) + { + fill_buffer(row); + write_buffer(); + } + + /// Insert values as a row. + /** This is the recommended way of inserting data. Pass your field values, + * of any convertible type. + */ + template void write_values(Ts const &...fields) + { + fill_buffer(fields...); + write_buffer(); + } + +private: + /// Stream a pre-quoted table name and columns list. + stream_to( + transaction_base &tx, std::string_view path, std::string_view columns); + + bool m_finished = false; + + /// Reusable buffer for a row. Saves doing an allocation for each row. + std::string m_buffer; + + /// Reusable buffer for converting/escaping a field. + std::string m_field_buf; + + /// Glyph scanner, for parsing the client encoding. + internal::glyph_scanner_func *m_scanner; + + /// Write a row of raw text-format data into the destination table. + void write_raw_line(std::string_view); + + /// Write a row of data from @c m_buffer into the destination table. + /** Resets the buffer for the next row. + */ + void write_buffer(); + + /// COPY encoding for a null field, plus subsequent separator. + static constexpr std::string_view null_field{"\\N\t"}; + + /// Estimate buffer space needed for a field which is always null. + template + static std::enable_if_t::always_null, std::size_t> + estimate_buffer(T const &) + { + return std::size(null_field); + } + + /// Estimate buffer space needed for field f. + /** The estimate is not very precise. We don't actually know how much space + * we'll need once the escaping comes in. + */ + template + static std::enable_if_t::always_null, std::size_t> + estimate_buffer(T const &field) + { + return is_null(field) ? std::size(null_field) : size_buffer(field); + } + + /// Append escaped version of @c data to @c m_buffer, plus a tab. + void escape_field_to_buffer(std::string_view data); + + /// Append string representation for @c f to @c m_buffer. + /** This is for the general case, where the field may contain a value. + * + * Also appends a tab. The tab is meant to be a separator, not a terminator, + * so if you write any fields at all, you'll end up with one tab too many + * at the end of the buffer. + */ + template + std::enable_if_t::always_null> + append_to_buffer(Field const &f) + { + // We append each field, terminated by a tab. That will leave us with + // one tab too many, assuming we write any fields at all; we remove that + // at the end. + if (is_null(f)) + { + // Easy. Append null and tab in one go. + m_buffer.append(null_field); + } + else + { + // Convert f into m_buffer. + + using traits = string_traits; + auto const budget{estimate_buffer(f)}; + auto const offset{std::size(m_buffer)}; + + if constexpr (std::is_arithmetic_v) + { + // Specially optimised for "safe" types, which never need any + // escaping. Convert straight into m_buffer. + + // The budget we get from size_buffer() includes room for the trailing + // zero, which we must remove. But we're also inserting tabs between + // fields, so we re-purpose the extra byte for that. + auto const total{offset + budget}; + m_buffer.resize(total); + auto const data{m_buffer.data()}; + char *const end{traits::into_buf(data + offset, data + total, f)}; + *(end - 1) = '\t'; + // Shrink to fit. Keep the tab though. + m_buffer.resize(static_cast(end - data)); + } + else if constexpr ( + std::is_same_v or + std::is_same_v or + std::is_same_v) + { + // This string may need escaping. + m_field_buf.resize(budget); + escape_field_to_buffer(f); + } + else + { + // This field needs to be converted to a string, and after that, + // escaped as well. + m_field_buf.resize(budget); + auto const data{m_field_buf.data()}; + escape_field_to_buffer( + traits::to_buf(data, data + std::size(m_field_buf), f)); + } + } + } + + /// Append string representation for a null field to @c m_buffer. + /** This special case is for types which are always null. + * + * Also appends a tab. The tab is meant to be a separator, not a terminator, + * so if you write any fields at all, you'll end up with one tab too many + * at the end of the buffer. + */ + template + std::enable_if_t::always_null> + append_to_buffer(Field const &) + { + m_buffer.append(null_field); + } + + /// Write raw COPY line into @c m_buffer, based on a container of fields. + template + std::enable_if_t> + fill_buffer(Container const &c) + { + // To avoid unnecessary allocations and deallocations, we run through c + // twice: once to determine how much buffer space we may need, and once to + // actually write it into the buffer. + std::size_t budget{0}; + for (auto const &f : c) budget += estimate_buffer(f); + m_buffer.reserve(budget); + for (auto const &f : c) append_to_buffer(f); + } + + /// Estimate how many buffer bytes we need to write tuple. + template + static std::size_t + budget_tuple(Tuple const &t, std::index_sequence) + { + return (estimate_buffer(std::get(t)) + ...); + } + + /// Write tuple of fields to @c m_buffer. + template + void append_tuple(Tuple const &t, std::index_sequence) + { + (append_to_buffer(std::get(t)), ...); + } + + /// Write raw COPY line into @c m_buffer, based on a tuple of fields. + template void fill_buffer(std::tuple const &t) + { + using indexes = std::make_index_sequence; + + m_buffer.reserve(budget_tuple(t, indexes{})); + append_tuple(t, indexes{}); + } + + /// Write raw COPY line into @c m_buffer, based on varargs fields. + template void fill_buffer(const Ts &...fields) + { + (..., append_to_buffer(fields)); + } + + constexpr static std::string_view s_classname{"stream_to"}; +}; + + +template +inline stream_to::stream_to( + transaction_base &tx, std::string_view table_name, Columns const &columns) : + stream_to{tx, table_name, std::begin(columns), std::end(columns)} +{} + + +template +inline stream_to::stream_to( + transaction_base &tx, std::string_view table_name, Iter columns_begin, + Iter columns_end) : + stream_to{ + tx, + tx.quote_name( + table_name, + separated_list(",", columns_begin, columns_end, [&tx](auto col) { + return tx.quote_name(*col); + }))} +{} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction new file mode 100644 index 000000000..e0d154903 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction @@ -0,0 +1,8 @@ +/** pqxx::subtransaction class. + * + * pqxx::subtransaction is a nested transaction, i.e. one inside a transaction. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/subtransaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction.hxx new file mode 100644 index 000000000..e66b7a7a8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/subtransaction.hxx @@ -0,0 +1,96 @@ +/* Definition of the pqxx::subtransaction class. + * + * pqxx::subtransaction is a nested transaction, i.e. one within a transaction. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/subtransaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_SUBTRANSACTION +#define PQXX_H_SUBTRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/dbtransaction.hxx" + +namespace pqxx +{ +/** + * @ingroup transactions + */ +/// "Transaction" nested within another transaction +/** A subtransaction can be executed inside a backend transaction, or inside + * another subtransaction. This can be useful when, for example, statements in + * a transaction may harmlessly fail and you don't want them to abort the + * entire transaction. Here's an example of how a temporary table may be + * dropped before re-creating it, without failing if the table did not exist: + * + * ```cxx + * void do_job(connection &C) + * { + * string const temptable = "fleetingtable"; + * + * work W(C, "do_job"); + * do_firstpart(W); + * + * // Attempt to delete our temporary table if it already existed. + * try + * { + * subtransaction S(W, "droptemp"); + * S.exec0("DROP TABLE " + temptable); + * S.commit(); + * } + * catch (undefined_table const &) + * { + * // Table did not exist. Which is what we were hoping to achieve anyway. + * // Carry on without regrets. + * } + * + * // S may have gone into a failed state and been destroyed, but the + * // upper-level transaction W is still fine. We can continue to use it. + * W.exec0("CREATE TEMP TABLE " + temptable + "(bar integer, splat + * varchar)"); + * + * do_lastpart(W); + * } + * ``` + * + * (This is just an example. If you really wanted to do drop a table without + * an error if it doesn't exist, you'd use DROP TABLE IF EXISTS.) + * + * There are no isolation levels inside a transaction. They are not needed + * because all actions within the same backend transaction are always performed + * sequentially anyway. + * + * @warning While the subtransaction is "live," you cannot execute queries or + * open streams etc. on its parent transaction. A transaction can have at most + * one object of a type derived from @ref pqxx::transaction_focus active on it + * at a time. + */ +class PQXX_LIBEXPORT subtransaction : public transaction_focus, + public dbtransaction +{ +public: + /// Nest a subtransaction nested in another transaction. + explicit subtransaction(dbtransaction &t, std::string_view tname = ""sv); + + /// Nest a subtransaction in another subtransaction. + explicit subtransaction(subtransaction &t, std::string_view name = ""sv); + + virtual ~subtransaction() noexcept override; + +private: + std::string quoted_name() const + { + return quote_name(transaction_focus::name()); + } + virtual void do_commit() override; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time new file mode 100644 index 000000000..85df05744 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time @@ -0,0 +1,6 @@ +/** Date/time string conversions. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/time.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time.hxx new file mode 100644 index 000000000..effed05e0 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/time.hxx @@ -0,0 +1,88 @@ +/** Support for date/time values. + * + * At the moment this supports dates, but not times. + */ +#ifndef PQXX_H_TIME +#define PQXX_H_TIME + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +#include "pqxx/internal/concat.hxx" +#include "pqxx/strconv.hxx" + + +#if defined(PQXX_HAVE_YEAR_MONTH_DAY) + +namespace pqxx +{ +using namespace std::literals; + +template<> +struct nullness + : no_null +{}; + + +/// String representation for a Gregorian date in ISO-8601 format. +/** @warning Experimental. There may still be design problems, particularly + * when it comes to BC years. + * + * PostgreSQL supports a choice of date formats, but libpqxx does not. The + * other formats in turn support a choice of "month before day" versus "day + * before month," meaning that it's not necessarily known which format a given + * date is supposed to be. So I repeat: ISO-8601-style format only! + * + * Invalid dates will not convert. This includes February 29 on non-leap + * years, which is why it matters that `year_month_day` represents a + * _Gregorian_ date. + * + * The range of years is limited. At the time of writing, PostgreSQL 14 + * supports years from 4713 BC to 294276 AD inclusive, and C++20 supports + * a range of 32767 BC to 32767 AD inclusive. So in practice, years must fall + * between 4713 BC and 32767 AD, inclusive. + * + * @warning Support for BC (or BCE) years is still experimental. I still need + * confirmation on this issue: it looks as if C++ years are astronomical years, + * which means they have a Year Zero. Regular BC/AD years do not have a year + * zero, so the year 1 AD follows directly after 1 BC. + * + * So, what to our calendars (and to PostgreSQL) is the year "0001 BC" seems to + * count as year "0" in a `std::chrono::year_month_day`. The year 0001 AD is + * still equal to 1 as you'd expect, and all AD years work normally, but all + * years before then are shifted by one. For instance, the year 543 BC would + * be -542 in C++. + */ +template<> struct PQXX_LIBEXPORT string_traits +{ + [[nodiscard]] static zview + to_buf(char *begin, char *end, std::chrono::year_month_day const &value) + { + return generic_to_buf(begin, end, value); + } + + static char * + into_buf(char *begin, char *end, std::chrono::year_month_day const &value); + + [[nodiscard]] static std::chrono::year_month_day + from_string(std::string_view text); + + [[nodiscard]] static std::size_t + size_buffer(std::chrono::year_month_day const &) noexcept + { + static_assert(int{(std::chrono::year::min)()} >= -99999); + static_assert(int{(std::chrono::year::max)()} <= 99999); + return 5 + 1 + 2 + 1 + 2 + std::size(s_bc) + 1; + } + +private: + /// The "BC" suffix for years before 1 AD. + static constexpr std::string_view s_bc{" BC"sv}; +}; +} // namespace pqxx +#endif // PQXX_HAVE_YEAR_MONTH_DAY +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction new file mode 100644 index 000000000..a7ae39d43 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction @@ -0,0 +1,8 @@ +/** pqxx::transaction class. + * + * pqxx::transaction represents a standard database transaction. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/transaction.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction.hxx new file mode 100644 index 000000000..e90917e38 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction.hxx @@ -0,0 +1,108 @@ +/* Definition of the pqxx::transaction class. + * pqxx::transaction represents a standard database transaction. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transaction instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TRANSACTION +#define PQXX_H_TRANSACTION + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/dbtransaction.hxx" + +namespace pqxx::internal +{ +/// Helper base class for the @ref transaction class template. +class PQXX_LIBEXPORT basic_transaction : public dbtransaction +{ +protected: + basic_transaction( + connection &c, zview begin_command, std::string_view tname); + basic_transaction(connection &c, zview begin_command, std::string &&tname); + basic_transaction(connection &c, zview begin_command); + + virtual ~basic_transaction() noexcept override = 0; + +private: + virtual void do_commit() override; +}; +} // namespace pqxx::internal + + +namespace pqxx +{ +/** + * @ingroup transactions + */ +//@{ + +/// Standard back-end transaction, templatised on isolation level. +/** This is the type you'll normally want to use to represent a transaction on + * the database. + * + * Usage example: double all wages. + * + * ```cxx + * extern connection C; + * work T(C); + * try + * { + * T.exec0("UPDATE employees SET wage=wage*2"); + * T.commit(); // NOTE: do this inside try block + * } + * catch (exception const &e) + * { + * cerr << e.what() << endl; + * T.abort(); // Usually not needed; same happens when T's life ends. + * } + * ``` + */ +template< + isolation_level ISOLATION = isolation_level::read_committed, + write_policy READWRITE = write_policy::read_write> +class transaction final : public internal::basic_transaction +{ +public: + /// Begin a transaction. + /** + * @param c Connection for this transaction to operate on. + * @param tname Optional name for transaction. Must begin with a letter and + * may contain letters and digits only. + */ + transaction(connection &c, std::string_view tname) : + internal::basic_transaction{ + c, internal::begin_cmd, tname} + {} + + /// Begin a transaction. + /** + * @param c Connection for this transaction to operate on. + * may contain letters and digits only. + */ + explicit transaction(connection &c) : + internal::basic_transaction{ + c, internal::begin_cmd} + {} + + virtual ~transaction() noexcept override { close(); } +}; + + +/// The default transaction type. +using work = transaction<>; + +/// Read-only transaction. +using read_transaction = + transaction; + +//@} +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base new file mode 100644 index 000000000..c39219aac --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base @@ -0,0 +1,9 @@ +/** Base for the transaction classes. + * + * pqxx::transaction_base defines the interface for any abstract class that + * represents a database transaction. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/transaction_base.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base.hxx new file mode 100644 index 000000000..4363cc56a --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_base.hxx @@ -0,0 +1,810 @@ +/* Common code and definitions for the transaction classes. + * + * pqxx::transaction_base defines the interface for any abstract class that + * represents a database transaction. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transaction_base instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TRANSACTION_BASE +#define PQXX_H_TRANSACTION_BASE + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +/* End-user programs need not include this file, unless they define their own + * transaction classes. This is not something the typical program should want + * to do. + * + * However, reading this file is worthwhile because it defines the public + * interface for the available transaction classes such as transaction and + * nontransaction. + */ + +#include "pqxx/connection.hxx" +#include "pqxx/internal/concat.hxx" +#include "pqxx/internal/encoding_group.hxx" +#include "pqxx/isolation.hxx" +#include "pqxx/result.hxx" +#include "pqxx/row.hxx" +#include "pqxx/stream_from.hxx" +#include "pqxx/util.hxx" + +namespace pqxx::internal::gate +{ +class transaction_subtransaction; +class transaction_sql_cursor; +class transaction_stream_to; +class transaction_transaction_focus; +} // namespace pqxx::internal::gate + + +namespace pqxx +{ +using namespace std::literals; + + +class transaction_focus; + + +/** + * @defgroup transactions Transaction classes + * + * All database access goes through instances of these classes. + * However, not all implementations of this interface need to provide full + * transactional integrity. + * + * Several implementations of this interface are shipped with libpqxx, + * including the plain transaction class, the entirely unprotected + * nontransaction, and the more cautious robusttransaction. + */ + +/// Interface definition (and common code) for "transaction" classes. +/** + * @ingroup transactions + * + * Abstract base class for all transaction types. + */ +class PQXX_LIBEXPORT PQXX_NOVTABLE transaction_base +{ +public: + transaction_base() = delete; + transaction_base(transaction_base const &) = delete; + transaction_base(transaction_base &&) = delete; + transaction_base &operator=(transaction_base const &) = delete; + transaction_base &operator=(transaction_base &&) = delete; + + virtual ~transaction_base() = 0; + + /// Commit the transaction. + /** Make the effects of this transaction definite. If you destroy a + * transaction without invoking its @ref commit() first, that will implicitly + * abort it. (For the @ref nontransaction class though, "commit" and "abort" + * really don't do anything, hence its name.) + * + * There is, however, a minute risk that you might lose your connection to + * the database at just the wrong moment here. In that case, libpqxx may be + * unable to determine whether the database was able to complete the + * transaction, or had to roll it back. In that scenario, @ref commit() will + * throw an in_doubt_error. There is a different transaction class called + * @ref robusttransaction which takes some special precautions to reduce this + * risk. + */ + void commit(); + + /// Abort the transaction. + /** No special effort is required to call this function; it will be called + * implicitly when the transaction is destructed. + */ + void abort(); + + /** + * @ingroup escaping-functions + * + * Use these when writing SQL queries that incorporate C++ values as SQL + * constants. + * + * The functions you see here are just convenience shortcuts to the same + * functions on the connection object. + */ + //@{ + /// Escape string for use as SQL string literal in this transaction. + template [[nodiscard]] auto esc(ARGS &&...args) const + { + return conn().esc(std::forward(args)...); + } + + /// Escape binary data for use as SQL string literal in this transaction. + /** Raw, binary data is treated differently from regular strings. Binary + * strings are never interpreted as text, so they may safely include byte + * values or byte sequences that don't happen to represent valid characters + * in the character encoding being used. + * + * The binary string does not stop at the first zero byte, as is the case + * with textual strings. Instead, it may contain zero bytes anywhere. If + * it happens to contain bytes that look like quote characters, or other + * things that can disrupt their use in SQL queries, they will be replaced + * with special escape sequences. + */ + template [[nodiscard]] auto esc_raw(ARGS &&...args) const + { + return conn().esc_raw(std::forward(args)...); + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string + unesc_raw(zview text) const + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return conn().unesc_raw(text); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard]] std::basic_string unesc_bin(zview text) + { + return conn().unesc_bin(text); + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string + unesc_raw(char const *text) const + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return conn().unesc_raw(text); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Unescape binary data, e.g. from a table field or notification payload. + /** Takes a binary string as escaped by PostgreSQL, and returns a restored + * copy of the original binary data. + */ + [[nodiscard]] std::basic_string unesc_bin(char const text[]) + { + return conn().unesc_bin(text); + } + + /// Represent object as SQL string, including quoting & escaping. + /** Nulls are recognized and represented as SQL nulls. */ + template [[nodiscard]] std::string quote(T const &t) const + { + return conn().quote(t); + } + + [[deprecated( + "Use std::basic_string instead of binarystring.")]] std::string + quote(binarystring const &t) const + { + return conn().quote(t.bytes_view()); + } + + /// Binary-escape and quote a binary string for use as an SQL constant. + [[deprecated("Use quote(std::basic_string_view).")]] std::string + quote_raw(unsigned char const bin[], std::size_t len) const + { + return quote(binary_cast(bin, len)); + } + + /// Binary-escape and quote a binary string for use as an SQL constant. + [[deprecated("Use quote(std::basic_string_view).")]] std::string + quote_raw(zview bin) const; + +#if defined(PQXX_HAVE_CONCEPTS) + /// Binary-escape and quote a binary string for use as an SQL constant. + /** For binary data you can also just use @ref quote(data). */ + template + [[nodiscard]] std::string quote_raw(DATA const &data) const + { + return conn().quote_raw(data); + } +#endif + + /// Escape an SQL identifier for use in a query. + [[nodiscard]] std::string quote_name(std::string_view identifier) const + { + return conn().quote_name(identifier); + } + + /// Escape string for literal LIKE match. + [[nodiscard]] std::string + esc_like(std::string_view bin, char escape_char = '\\') const + { + return conn().esc_like(bin, escape_char); + } + //@} + + /** + * @name Command execution + * + * There are many functions for executing (or "performing") a command (or + * "query"). This is the most fundamental thing you can do with the library, + * and you always do it from a transaction class. + * + * Command execution can throw many types of exception, including sql_error, + * broken_connection, and many sql_error subtypes such as + * feature_not_supported or insufficient_privilege. But any exception thrown + * by the C++ standard library may also occur here. All exceptions you will + * see libpqxx throw are derived from std::exception. + * + * One unusual feature in libpqxx is that you can give your query a name or + * description. This does not mean anything to the database, but sometimes + * it can help libpqxx produce more helpful error messages, making problems + * in your code easier to debug. + * + * Many of the execution functions used to accept a `desc` argument, a + * human-readable description of the statement for use in error messages. + * This could make failures easier to debug. Future versions will use + * C++20's `std::source_location` to identify the failing statement. + */ + //@{ + + /// Execute a command. + /** + * @param query Query or command to execute. + * @param desc Optional identifier for query, to help pinpoint SQL errors. + * @return A result set describing the query's or command's result. + */ + [[deprecated("The desc parameter is going away.")]] result + exec(std::string_view query, std::string_view desc); + + /// Execute a command. + /** + * @param query Query or command to execute. + * @return A result set describing the query's or command's result. + */ + result exec(std::string_view query) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec(query, std::string_view{}); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Execute a command. + /** + * @param query Query or command to execute. + * @param desc Optional identifier for query, to help pinpoint SQL errors. + * @return A result set describing the query's or command's result. + */ + [[deprecated( + "Pass your query as a std::string_view, not stringstream.")]] result + exec(std::stringstream const &query, std::string_view desc) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec(query.str(), desc); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Execute command, which should return zero rows of data. + /** Works like @ref exec, but fails if the result contains data. It still + * returns a result, however, which may contain useful metadata. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + [[deprecated("The desc parameter is going away.")]] result + exec0(zview query, std::string_view desc) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec_n(0, query, desc); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Execute command, which should return zero rows of data. + /** Works like @ref exec, but fails if the result contains data. It still + * returns a result, however, which may contain useful metadata. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + result exec0(zview query) { return exec_n(0, query); } + + /// Execute command returning a single row of data. + /** Works like @ref exec, but requires the result to contain exactly one row. + * The row can be addressed directly, without the need to find the first row + * in a result set. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + [[deprecated("The desc parameter is going away.")]] row + exec1(zview query, std::string_view desc) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec_n(1, query, desc).front(); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Execute command returning a single row of data. + /** Works like @ref exec, but requires the result to contain exactly one row. + * The row can be addressed directly, without the need to find the first row + * in a result set. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + row exec1(zview query) { return exec_n(1, query).front(); } + + /// Execute command, expect given number of rows. + /** Works like @ref exec, but checks that the result has exactly the expected + * number of rows. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + [[deprecated("The desc parameter is going away.")]] result + exec_n(result::size_type rows, zview query, std::string_view desc); + + /// Execute command, expect given number of rows. + /** Works like @ref exec, but checks that the result has exactly the expected + * number of rows. + * + * @throw unexpected_rows If the query returned the wrong number of rows. + */ + result exec_n(result::size_type rows, zview query) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + return exec_n(rows, query, std::string_view{}); +#include "pqxx/internal/ignore-deprecated-post.hxx" + } + + /// Perform query, expecting exactly 1 row with 1 field, and convert it. + /** This is convenience shorthand for querying exactly one value from the + * database. It returns that value, converted to the type you specify. + */ + template + [[deprecated("The desc parameter is going away.")]] TYPE + query_value(zview query, std::string_view desc) + { +#include "pqxx/internal/ignore-deprecated-pre.hxx" + row const r{exec1(query, desc)}; +#include "pqxx/internal/ignore-deprecated-post.hxx" + if (std::size(r) != 1) + throw usage_error{internal::concat( + "Queried single value from result with ", std::size(r), " columns.")}; + return r[0].as(); + } + + /// Perform query, expecting exactly 1 row with 1 field, and convert it. + /** This is convenience shorthand for querying exactly one value from the + * database. It returns that value, converted to the type you specify. + */ + template TYPE query_value(zview query) + { + row const r{exec1(query)}; + if (std::size(r) != 1) + throw usage_error{internal::concat( + "Queried single value from result with ", std::size(r), " columns.")}; + return r[0].as(); + } + + /// Execute a query, and loop over the results row by row. + /** Converts the rows to `std::tuple`, of the column types you specify. + * + * Use this with a range-based "for" loop. It executes the query, and + * directly maps the resulting rows onto a `std::tuple` of the types you + * specify. It starts before all the data from the server is in, so if your + * network connection to the server breaks while you're iterating, you'll get + * an exception partway through. + * + * The stream lives entirely within the lifetime of the transaction. Make + * sure you destroy the stream before you destroy the transaction. Either + * iterate the stream all the way to the end, or destroy first the stream + * and then the transaction without touching either in any other way. Until + * the stream has finished, the transaction is in a special state where it + * cannot execute queries. + * + * As a special case, tuple may contain `std::string_view` fields, but the + * strings to which they point will only remain valid until you extract the + * next row. After that, the memory holding the string may be overwritten or + * deallocated. + * + * If any of the columns can be null, and the C++ type to which it translates + * does not have a null value, wrap the type in `std::optional` (or if + * you prefer, `std::shared_ptr` or `std::unique_ptr)`. These templates + * do recognise null values, and libpqxx will know how to convert to them. + * + * The connection is in a special state until the iteration finishes. So if + * it does not finish due to a `break` or a `return` or an exception, then + * the entire connection becomes effectively unusable. + * + * Querying in this way is faster than the `exec()` methods for larger + * results (but slower for small ones). You can start processing rows before + * the full result is in. Also, `stream()` scales better in terms of memory + * usage. Where @ref exec() reads the entire result into memory at once, + * `stream()` will read and process one row at at a time. + * + * Your query executes as part of a COPY command, not as a stand-alone query, + * so there are limitations to what you can do in the query. It can be + * either a SELECT or VALUES query; or an INSERT, UPDATE, or DELETE with a + * RETURNING clause. See the documentation for PostgreSQL's COPY command for + * the details: + * + * https://www.postgresql.org/docs/current/sql-copy.html + * + * Iterating in this way does require each of the field types you pass to be + * default-constructible, copy-constructible, and assignable. These + * requirements may be loosened once libpqxx moves on to C++20. + */ + template + [[nodiscard]] auto stream(std::string_view query) & + { + // Tricky: std::make_unique() supports constructors but not RVO functions. + return pqxx::internal::owning_stream_input_iteration{ + std::unique_ptr{ + new stream_from{stream_from::query(*this, query)}}}; + } + + // C++20: Concept like std::invocable, but without specifying param types. + /// Perform a streaming query, and for each result row, call `func`. + /** Here, `func` can be a function, a `std::function`, a lambda, or an + * object that supports the function call operator. Of course `func` must + * have an unambiguous signature; it can't be overloaded or generic. + * + * The `for_each` function executes `query` in a stream using + * @ref pqxx::stream_from. Every time a row of data comes in from the + * server, it converts the row's fields to the types of `func`'s respective + * parameters, and calls `func` with those values. + * + * This will not work for all queries, but straightforward `SELECT` and + * `UPDATE ... RETURNING` queries should work. Consult the documentation for + * @ref pqxx::stream_from and PostgreSQL's underlying `COPY` command for the + * full details. + * + * Streaming a query like this is likely to be slower than the @ref exec() + * functions for small result sets, but faster for large result sets. So if + * performance matters, you'll want to use `for_each` if you query large + * amounts of data, but not if you do lots of queries with small outputs. + */ + template + inline auto for_each(std::string_view query, CALLABLE &&func) + { + using param_types = + pqxx::internal::strip_types_t>; + param_types const *const sample{nullptr}; + auto data_stream{stream_like(query, sample)}; + for (auto const &fields : data_stream) std::apply(func, fields); + } + + /** + * @name Parameterized statements + * + * You'll often need parameters in the queries you execute: "select the + * car with this licence plate." If the parameter is a string, you need to + * quote it and escape any special characters inside it, or it may become a + * target for an SQL injection attack. If it's an integer (for example), + * you need to convert it to a string, but in the database's format, without + * locale-specific niceties like "," separators between the thousands. + * + * Parameterised statements are an easier and safer way to do this. They're + * like prepared statements, but for a single use. You don't need to name + * them, and you don't need to prepare them first. + * + * Your query will include placeholders like `$1` and `$2` etc. in the places + * where you want the arguments to go. Then, you pass the argument values + * and the actual query is constructed for you. + * + * Pass the exact right number of parameters, and in the right order. The + * parameters in the query don't have to be neatly ordered from `$1` to + * `$2` to `$3` - but you must pass the argument for `$1` first, the one + * for `$2` second, etc. + * + * @warning Beware of "nul" bytes. Any string you pass as a parameter will + * end at the first char with value zero. If you pass a string that contains + * a zero byte, the last byte in the value will be the one just before the + * zero. + */ + //@{ + /// Execute an SQL statement with parameters. + template result exec_params(zview query, Args &&...args) + { + params pp(args...); + return internal_exec_params(query, pp.make_c_params()); + } + + // Execute parameterised statement, expect a single-row result. + /** @throw unexpected_rows if the result does not consist of exactly one row. + */ + template row exec_params1(zview query, Args &&...args) + { + return exec_params_n(1, query, std::forward(args)...).front(); + } + + // Execute parameterised statement, expect a result with zero rows. + /** @throw unexpected_rows if the result contains rows. + */ + template result exec_params0(zview query, Args &&...args) + { + return exec_params_n(0, query, std::forward(args)...); + } + + // Execute parameterised statement, expect exactly a given number of rows. + /** @throw unexpected_rows if the result contains the wrong number of rows. + */ + template + result exec_params_n(std::size_t rows, zview query, Args &&...args) + { + auto const r{exec_params(query, std::forward(args)...)}; + check_rowcount_params(rows, std::size(r)); + return r; + } + //@} + + /** + * @name Prepared statements + * + * These are very similar to parameterised statements. The difference is + * that you prepare them in advance, giving them identifying names. You can + * then call them by these names, passing in the argument values appropriate + * for that call. + * + * You prepare a statement on the connection, using + * @ref pqxx::connection::prepare(). But you then call the statement in a + * transaction, using the functions you see here. + * + * Never try to prepare, execute, or unprepare a prepared statement manually + * using direct SQL queries when you also use the libpqxx equivalents. For + * any given statement, either prepare, manage, and execute it through the + * dedicated libpqxx functions; or do it all directly in SQL. Don't mix the + * two, or the code may get confused. + * + * See \ref prepared for a full discussion. + * + * @warning Beware of "nul" bytes. Any string you pass as a parameter will + * end at the first char with value zero. If you pass a string that contains + * a zero byte, the last byte in the value will be the one just before the + * zero. If you need a zero byte, you're dealing with binary strings, not + * regular strings. Represent binary strings on the SQL side as `BYTEA` + * (or as large objects). On the C++ side, use types like + * `std::basic_string` or `std::basic_string_view` + * or (in C++20) `std::vector`. Also, consider large objects on + * the SQL side and @ref blob on the C++ side. + */ + //@{ + + /// Execute a prepared statement, with optional arguments. + template + result exec_prepared(zview statement, Args &&...args) + { + params pp(args...); + return internal_exec_prepared(statement, pp.make_c_params()); + } + + /// Execute a prepared statement, and expect a single-row result. + /** @throw pqxx::unexpected_rows if the result was not exactly 1 row. + */ + template + row exec_prepared1(zview statement, Args &&...args) + { + return exec_prepared_n(1, statement, std::forward(args)...).front(); + } + + /// Execute a prepared statement, and expect a result with zero rows. + /** @throw pqxx::unexpected_rows if the result contained rows. + */ + template + result exec_prepared0(zview statement, Args &&...args) + { + return exec_prepared_n(0, statement, std::forward(args)...); + } + + /// Execute a prepared statement, expect a result with given number of rows. + /** @throw pqxx::unexpected_rows if the result did not contain exactly the + * given number of rows. + */ + template + result + exec_prepared_n(result::size_type rows, zview statement, Args &&...args) + { + auto const r{exec_prepared(statement, std::forward(args)...)}; + check_rowcount_prepared(statement, rows, std::size(r)); + return r; + } + + //@} + + /** + * @name Error/warning output + */ + //@{ + /// Have connection process a warning message. + void process_notice(char const msg[]) const { m_conn.process_notice(msg); } + /// Have connection process a warning message. + void process_notice(zview msg) const { m_conn.process_notice(msg); } + //@} + + /// The connection in which this transaction lives. + [[nodiscard]] constexpr connection &conn() const noexcept { return m_conn; } + + /// Set session variable using SQL "SET" command. + /** @deprecated To set a transaction-local variable, execute an SQL `SET` + * command. To set a session variable, use the connection's + * @ref set_session_var function. + * + * @warning When setting a string value, you must make sure that the string + * is "safe." If you call @ref quote() on the string, it will return a + * safely escaped and quoted version for use as an SQL literal. + * + * @warning This function executes SQL. Do not try to set or get variables + * while a pipeline or table stream is active. + * + * @param var The variable to set. + * @param value The new value to store in the variable. This can be any SQL + * expression. + */ + [[deprecated( + "Set transaction-local variables using SQL SET statements.")]] void + set_variable(std::string_view var, std::string_view value); + + /// Read session variable using SQL "SHOW" command. + /** @warning This executes SQL. Do not try to set or get variables while a + * pipeline or table stream is active. + */ + [[deprecated("Read variables using SQL SHOW statements.")]] std::string + get_variable(std::string_view); + + // C++20: constexpr. + /// Transaction name, if you passed one to the constructor; or empty string. + [[nodiscard]] std::string_view name() const &noexcept { return m_name; } + +protected: + /// Create a transaction (to be called by implementation classes only). + /** The name, if nonempty, must begin with a letter and may contain letters + * and digits only. + */ + transaction_base( + connection &c, std::string_view tname, + std::shared_ptr rollback_cmd) : + m_conn{c}, m_name{tname}, m_rollback_cmd{rollback_cmd} + {} + + /// Create a transaction (to be called by implementation classes only). + /** Its rollback command will be "ROLLBACK". + * + * The name, if nonempty, must begin with a letter and may contain letters + * and digits only. + */ + transaction_base(connection &c, std::string_view tname); + + /// Create a transaction (to be called by implementation classes only). + explicit transaction_base(connection &c); + + /// Register this transaction with the connection. + void register_transaction(); + + /// End transaction. To be called by implementing class' destructor. + void close() noexcept; + + /// To be implemented by derived implementation class: commit transaction. + virtual void do_commit() = 0; + + /// Transaction type-specific way of aborting a transaction. + /** @warning This will become "final", since this function can be called + * from the implementing class destructor. + */ + virtual void do_abort(); + + /// Set the rollback command. + void set_rollback_cmd(std::shared_ptr cmd) + { + m_rollback_cmd = cmd; + } + + /// Execute query on connection directly. + result direct_exec(std::string_view, std::string_view desc = ""sv); + result + direct_exec(std::shared_ptr, std::string_view desc = ""sv); + +private: + enum class status + { + active, + aborted, + committed, + in_doubt + }; + + PQXX_PRIVATE void check_pending_error(); + + result + internal_exec_prepared(zview statement, internal::c_params const &args); + + result internal_exec_params(zview query, internal::c_params const &args); + + /// Throw unexpected_rows if prepared statement returned wrong no. of rows. + void check_rowcount_prepared( + zview statement, result::size_type expected_rows, + result::size_type actual_rows); + + /// Throw unexpected_rows if wrong row count from parameterised statement. + void + check_rowcount_params(std::size_t expected_rows, std::size_t actual_rows); + + /// Describe this transaction to humans, e.g. "transaction 'foo'". + [[nodiscard]] std::string description() const; + + friend class pqxx::internal::gate::transaction_transaction_focus; + PQXX_PRIVATE void register_focus(transaction_focus *); + PQXX_PRIVATE void unregister_focus(transaction_focus *) noexcept; + PQXX_PRIVATE void register_pending_error(zview) noexcept; + PQXX_PRIVATE void register_pending_error(std::string &&) noexcept; + + /// Like @ref stream(), but takes a tuple rather than a parameter pack. + template + auto stream_like(std::string_view query, std::tuple const *) + { + return stream(query); + } + + connection &m_conn; + + /// Current "focus": a pipeline, a nested transaction, a stream... + /** This pointer is used for only one purpose: sanity checks against mistakes + * such as opening one while another is still active. + */ + transaction_focus const *m_focus = nullptr; + + status m_status = status::active; + bool m_registered = false; + std::string m_name; + std::string m_pending_error; + + /// SQL command for aborting this type of transaction. + std::shared_ptr m_rollback_cmd; + + static constexpr std::string_view s_type_name{"transaction"sv}; +}; + + +// C++20: Can borrowed_range help? +/// Forbidden specialisation: underlying buffer immediately goes out of scope. +template<> +std::string_view transaction_base::query_value( + zview query, std::string_view desc) = delete; +/// Forbidden specialisation: underlying buffer immediately goes out of scope. +template<> +zview transaction_base::query_value( + zview query, std::string_view desc) = delete; + +} // namespace pqxx + + +namespace pqxx::internal +{ +/// The SQL command for starting a given type of transaction. +template +extern const zview begin_cmd; + +// These are not static members, so "constexpr" does not imply "inline". +template<> +inline constexpr zview begin_cmd{ + "BEGIN"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN READ ONLY"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN ISOLATION LEVEL REPEATABLE READ"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN ISOLATION LEVEL SERIALIZABLE"_zv}; +template<> +inline constexpr zview begin_cmd{ + "BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY"_zv}; +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus new file mode 100644 index 000000000..fe78a9bcc --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus @@ -0,0 +1,7 @@ +/** + * Transaction focus: types which monopolise a transaction's attention. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/types.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus.hxx new file mode 100644 index 000000000..0707e3cc4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transaction_focus.hxx @@ -0,0 +1,89 @@ +/** Transaction focus: types which monopolise a transaction's attention. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TRANSACTION_FOCUS +#define PQXX_H_TRANSACTION_FOCUS + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include "pqxx/util.hxx" + +namespace pqxx +{ +/// Base class for things that monopolise a transaction's attention. +/** You probably won't need to use this class. But it can be useful to _know_ + * that a given libpqxx class is derived from it. + * + * Pipelines, SQL statements, and data streams are examples of classes derived + * from `transaction_focus`. For any given transaction, only one object of + * such a class can be active at any given time. + */ +class PQXX_LIBEXPORT transaction_focus +{ +public: + transaction_focus( + transaction_base &t, std::string_view cname, std::string_view oname) : + m_trans{t}, m_classname{cname}, m_name{oname} + {} + + transaction_focus( + transaction_base &t, std::string_view cname, std::string &&oname) : + m_trans{t}, m_classname{cname}, m_name{std::move(oname)} + {} + + transaction_focus(transaction_base &t, std::string_view cname) : + m_trans{t}, m_classname{cname} + {} + + transaction_focus() = delete; + transaction_focus(transaction_focus const &) = delete; + transaction_focus &operator=(transaction_focus const &) = delete; + + /// Class name, for human consumption. + [[nodiscard]] constexpr std::string_view classname() const noexcept + { + return m_classname; + } + + /// Name for this object, if the caller passed one; empty string otherwise. + [[nodiscard]] std::string_view name() const &noexcept { return m_name; } + + [[nodiscard]] std::string description() const + { + return pqxx::internal::describe_object(m_classname, m_name); + } + + /// Can't move a transaction_focus. + /** Moving the transaction_focus would break the transaction's reference back + * to the object. + */ + transaction_focus(transaction_focus &&) = delete; + + /// Can't move a transaction_focus. + /** Moving the transaction_focus would break the transaction's reference back + * to the object. + */ + transaction_focus &operator=(transaction_focus &&) = delete; + +protected: + void register_me(); + void unregister_me() noexcept; + void reg_pending_error(std::string const &) noexcept; + bool registered() const noexcept { return m_registered; } + + transaction_base &m_trans; + +private: + bool m_registered = false; + std::string_view m_classname; + std::string m_name; +}; +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor new file mode 100644 index 000000000..29d1b9640 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor @@ -0,0 +1,8 @@ +/** pqxx::transactor class. + * + * pqxx::transactor is a framework-style wrapper for safe transactions. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/transactor.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor.hxx new file mode 100644 index 000000000..eefd04ba1 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/transactor.hxx @@ -0,0 +1,147 @@ +/* Transactor framework, a wrapper for safely retryable transactions. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transactor instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TRANSACTOR +#define PQXX_H_TRANSACTOR + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include + +#include "pqxx/connection.hxx" +#include "pqxx/transaction.hxx" + +namespace pqxx +{ +/** + * @defgroup transactor Transactor framework + * + * Sometimes a transaction can fail for completely transient reasons, such as a + * conflict with another transaction in SERIALIZABLE isolation. The right way + * to handle those failures is often just to re-run the transaction from + * scratch. + * + * For example, your REST API might be handling each HTTP request in its own + * database transaction, and if this kind of transient failure happens, you + * simply want to "replay" the whole request, in a fresh transaction. + * + * You won't necessarily want to execute the exact same SQL commands with the + * exact same data. Some of your SQL statements may depend on state that can + * vary between retries. Data in the database may already have changed, for + * instance. So instead of dumbly replaying the SQL, you re-run the same + * application code that produced those SQL commands, from the start. + * + * The transactor framework makes it a little easier for you to do this safely, + * and avoid typical pitfalls. You encapsulate the work that you want to do + * into a callable that you pass to the @ref perform function. + * + * Here's how it works. You write your transaction code as a lambda or + * function, which creates its own transaction object, does its work, and + * commits at the end. You pass that callback to @ref pqxx::perform, which + * runs it for you. + * + * If there's a failure inside your callback, there will be an exception. Your + * transaction object goes out of scope and gets destroyed, so that it aborts + * implicitly. Seeing this, @ref perform tries running your callback again. It + * stops doing that when the callback succeeds, or when it has failed too many + * times, or when there's an error that leaves the database in an unknown + * state, such as a lost connection just while we're waiting for the database + * to confirm a commit. It all depends on the type of exception. + * + * The callback takes no arguments. If you're using lambdas, the easy way to + * pass arguments is for the lambda to "capture" them from your variables. Or, + * if you're using functions, you may want to use `std::bind`. + * + * Once your callback succeeds, it can return a result, and @ref perform will + * return that result back to you. + */ +//@{ + +/// Simple way to execute a transaction with automatic retry. +/** + * Executes your transaction code as a callback. Repeats it until it completes + * normally, or it throws an error other than the few libpqxx-generated + * exceptions that the framework understands, or after a given number of failed + * attempts, or if the transaction ends in an "in-doubt" state. + * + * (An in-doubt state is one where libpqxx cannot determine whether the server + * finally committed a transaction or not. This can happen if the network + * connection to the server is lost just while we're waiting for its reply to + * a "commit" statement. The server may have completed the commit, or not, but + * it can't tell you because there's no longer a connection. + * + * Using this still takes a bit of care. If your callback makes use of data + * from the database, you'll probably have to query that data within your + * callback. If the attempt to perform your callback fails, and the framework + * tries again, you'll be in a new transaction and the data in the database may + * have changed under your feet. + * + * Also be careful about changing variables or data structures from within + * your callback. The run may still fail, and perhaps get run again. The + * ideal way to do it (in most cases) is to return your result from your + * callback, and change your program's data state only after @ref perform + * completes successfully. + * + * @param callback Transaction code that can be called with no arguments. + * @param attempts Maximum number of times to attempt performing callback. + * Must be greater than zero. + * @return Whatever your callback returns. + */ +template +inline auto perform(TRANSACTION_CALLBACK &&callback, int attempts = 3) + -> std::invoke_result_t +{ + if (attempts <= 0) + throw std::invalid_argument{ + "Zero or negative number of attempts passed to pqxx::perform()."}; + + for (; attempts > 0; --attempts) + { + try + { + return std::invoke(callback); + } + catch (in_doubt_error const &) + { + // Not sure whether transaction went through or not. The last thing in + // the world that we should do now is try again! + throw; + } + catch (statement_completion_unknown const &) + { + // Not sure whether our last statement succeeded. Don't risk running it + // again. + throw; + } + catch (broken_connection const &) + { + // Connection failed. May be worth retrying, if the transactor opens its + // own connection. + if (attempts <= 1) + throw; + continue; + } + catch (transaction_rollback const &) + { + // Some error that may well be transient, such as serialization failure + // or deadlock. Worth retrying. + if (attempts <= 1) + throw; + continue; + } + } + throw pqxx::internal_error{"No outcome reached on perform()."}; +} +} // namespace pqxx +//@} +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types new file mode 100644 index 000000000..23a5caae1 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types @@ -0,0 +1,7 @@ +/** + * Basic typedefs and forward declarations. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/types.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types.hxx new file mode 100644 index 000000000..f95b598f8 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/types.hxx @@ -0,0 +1,173 @@ +/* Basic type aliases and forward declarations. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_TYPES +#define PQXX_H_TYPES + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include + +#if defined(PQXX_HAVE_CONCEPTS) && __has_include() +# include +#endif + + +namespace pqxx +{ +/// Number of rows in a result set. +using result_size_type = int; + +/// Difference between result sizes. +using result_difference_type = int; + +/// Number of fields in a row of database data. +using row_size_type = int; + +/// Difference between row sizes. +using row_difference_type = int; + +/// Number of bytes in a field of database data. +using field_size_type = std::size_t; + +/// Number of bytes in a large object. +using large_object_size_type = int64_t; + + +// Forward declarations, to help break compilation dependencies. +// These won't necessarily include all classes in libpqxx. +class binarystring; +class connection; +class const_result_iterator; +class const_reverse_result_iterator; +class const_reverse_row_iterator; +class const_row_iterator; +class dbtransaction; +class field; +class largeobjectaccess; +class notification_receiver; +struct range_error; +class result; +class row; +class stream_from; +class transaction_base; + +/// Marker for @ref stream_from constructors: "stream from table." +/** @deprecated Use @ref stream_from::table() instead. + */ +struct from_table_t +{}; + +/// Marker for @ref stream_from constructors: "stream from query." +/** @deprecated Use @ref stream_from::query() instead. + */ +struct from_query_t +{}; + + +/// Format code: is data text or binary? +/** Binary-compatible with libpq's format codes. + */ +enum class format : int +{ + text = 0, + binary = 1, +}; + + +/// Remove any constness, volatile, and reference-ness from a type. +/** @deprecated In C++20 we'll replace this with std::remove_cvref. + */ +template +using strip_t = std::remove_cv_t>; + + +#if defined(PQXX_HAVE_CONCEPTS) +/// The type of a container's elements. +/** At the time of writing there's a similar thing in `std::experimental`, + * which we may or may not end up using for this. + */ +template +using value_type = strip_t()))>; +#else // PQXX_HAVE_CONCEPTS +/// The type of a container's elements. +/** At the time of writing there's a similar thing in `std::experimental`, + * which we may or may not end up using for this. + */ +template +using value_type = strip_t()))>; +#endif // PQXX_HAVE_CONCEPTS + + +#if defined(PQXX_HAVE_CONCEPTS) +/// Concept: Any type that we can read as a string of `char`. +template +concept char_string = std::ranges::contiguous_range and + std::same_as < strip_t>, +char > ; + +/// Concept: Anything we can iterate to get things we can read as strings. +template +concept char_strings = + std::ranges::range and char_string>>; + +/// Concept: Anything we might want to treat as binary data. +template +concept potential_binary = std::ranges::contiguous_range and + (sizeof(value_type) == 1); +#endif // PQXX_HAVE_CONCEPTS + + +// C++20: Retire these compatibility definitions. +#if defined(PQXX_HAVE_CONCEPTS) + +/// Template argument type for a range. +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_RANGE_ARG std::ranges::range + +/// Template argument type for @ref char_string. +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_CHAR_STRING_ARG pqxx::char_string + +/// Template argument type for @ref char_strings +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_CHAR_STRINGS_ARG pqxx::char_strings + +#else // PQXX_HAVE_CONCEPTS + +/// Template argument type for a range. +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_RANGE_ARG typename + +/// Template argument type for @ref char_string. +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_CHAR_STRING_ARG typename + +/// Template argument type for @ref char_strings +/** This is a concept, so only available in C++20 or better. In pre-C++20 + * environments it's just an alias for @ref typename. + */ +# define PQXX_CHAR_STRINGS_ARG typename + +#endif // PQXX_HAVE_CONCEPTS +} // namespace pqxx +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util new file mode 100644 index 000000000..6d85ab611 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util @@ -0,0 +1,6 @@ +/** Various utility definitions for libpqxx. + */ +// Actual definitions in .hxx file so editors and such recognize file type +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/util.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util.hxx new file mode 100644 index 000000000..4aa5ecf57 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/util.hxx @@ -0,0 +1,521 @@ +/* Various utility definitions for libpqxx. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/util instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_UTIL +#define PQXX_H_UTIL + +#if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#endif + +#include "pqxx/except.hxx" +#include "pqxx/internal/encodings.hxx" +#include "pqxx/types.hxx" +#include "pqxx/version.hxx" + + +/// The home of all libpqxx classes, functions, templates, etc. +namespace pqxx +{} + +#include + + +/// Internal items for libpqxx' own use. Do not use these yourself. +namespace pqxx::internal +{ + +// C++20: Retire wrapper. +/// Same as `std::cmp_less`, or a workaround where that's not available. +template +inline constexpr bool cmp_less(LEFT lhs, RIGHT rhs) noexcept +{ +#if defined(PQXX_HAVE_CMP) + return std::cmp_less(lhs, rhs); +#else + // We need a variable just because lgtm.com gives off a false positive + // warning when we compare the values directly. It considers that a + // "self-comparison." + constexpr bool left_signed{std::is_signed_v}; + if constexpr (left_signed == std::is_signed_v) + return lhs < rhs; + else if constexpr (std::is_signed_v) + return (lhs <= 0) ? true : (std::make_unsigned_t(lhs) < rhs); + else + return (rhs <= 0) ? false : (lhs < std::make_unsigned_t(rhs)); +#endif +} + + +// C++20: Retire wrapper. +/// C++20 std::cmp_greater, or workaround if not available. +template +inline constexpr bool cmp_greater(LEFT lhs, RIGHT rhs) noexcept +{ +#if defined(PQXX_HAVE_CMP) + return std::cmp_greater(lhs, rhs); +#else + return cmp_less(rhs, lhs); +#endif +} + + +// C++20: Retire wrapper. +/// C++20 std::cmp_less_equal, or workaround if not available. +template +inline constexpr bool cmp_less_equal(LEFT lhs, RIGHT rhs) noexcept +{ +#if defined(PQXX_HAVE_CMP) + return std::cmp_less_equal(lhs, rhs); +#else + return not cmp_less(rhs, lhs); +#endif +} + + +// C++20: Retire wrapper. +/// C++20 std::cmp_greater_equal, or workaround if not available. +template +inline constexpr bool cmp_greater_equal(LEFT lhs, RIGHT rhs) noexcept +{ +#if defined(PQXX_HAVE_CMP) + return std::cmp_greater_equal(lhs, rhs); +#else + return not cmp_less(lhs, rhs); +#endif +} + + +/// Efficiently concatenate two strings. +/** This is a special case of concatenate(), needed because dependency + * management does not let us use that function here. + */ +[[nodiscard]] inline std::string cat2(std::string_view x, std::string_view y) +{ + std::string buf; + auto const xs{std::size(x)}, ys{std::size(y)}; + buf.resize(xs + ys); + x.copy(std::data(buf), xs); + y.copy(std::data(buf) + xs, ys); + return buf; +} +} // namespace pqxx::internal + + +namespace pqxx +{ +using namespace std::literals; + +/// Suppress compiler warning about an unused item. +template inline constexpr void ignore_unused(T &&...) noexcept +{} + + +/// Cast a numeric value to another type, or throw if it underflows/overflows. +/** Both types must be arithmetic types, and they must either be both integral + * or both floating-point types. + */ +template +inline TO check_cast(FROM value, std::string_view description) +{ + static_assert(std::is_arithmetic_v); + static_assert(std::is_arithmetic_v); + static_assert(std::is_integral_v == std::is_integral_v); + + // The rest of this code won't quite work for bool, but bool is trivially + // convertible to other arithmetic types as far as I can see. + if constexpr (std::is_same_v) + return static_cast(value); + + // Depending on our "if constexpr" conditions, this parameter may not be + // needed. Some compilers will warn. + ignore_unused(description); + + using from_limits = std::numeric_limits; + using to_limits = std::numeric_limits; + if constexpr (std::is_signed_v) + { + if constexpr (std::is_signed_v) + { + if (value < to_limits::lowest()) + throw range_error{internal::cat2("Cast underflow: "sv, description)}; + } + else + { + // FROM is signed, but TO is not. Treat this as a special case, because + // there may not be a good broader type in which the compiler can even + // perform our check. + if (value < 0) + throw range_error{internal::cat2( + "Casting negative value to unsigned type: "sv, description)}; + } + } + else + { + // No need to check: the value is unsigned so can't fall below the range + // of the TO type. + } + + if constexpr (std::is_integral_v) + { + using unsigned_from = std::make_unsigned_t; + using unsigned_to = std::make_unsigned_t; + constexpr auto from_max{static_cast((from_limits::max)())}; + constexpr auto to_max{static_cast((to_limits::max)())}; + if constexpr (from_max > to_max) + { + if (internal::cmp_greater(value, to_max)) + throw range_error{internal::cat2("Cast overflow: "sv, description)}; + } + } + else if constexpr ((from_limits::max)() > (to_limits::max)()) + { + if (value > (to_limits::max)()) + throw range_error{internal::cat2("Cast overflow: ", description)}; + } + + return static_cast(value); +} + + +/** Check library version at link time. + * + * Ensures a failure when linking an application against a radically + * different libpqxx version than the one against which it was compiled. + * + * Sometimes application builds fail in unclear ways because they compile + * using headers from libpqxx version X, but then link against libpqxx + * binary version Y. A typical scenario would be one where you're building + * against a libpqxx which you have built yourself, but a different version + * is installed on the system. + * + * The check_library_version template is declared for any library version, + * but only actually defined for the version of the libpqxx binary against + * which the code is linked. + * + * If the library binary is a different version than the one declared in + * these headers, then this call will fail to link: there will be no + * definition for the function with these exact template parameter values. + * There will be a definition, but the version in the parameter values will + * be different. + */ +inline PQXX_PRIVATE void check_version() noexcept +{ + // There is no particular reason to do this here in @ref connection, except + // to ensure that every meaningful libpqxx client will execute it. The call + // must be in the execution path somewhere or the compiler won't try to link + // it. We can't use it to initialise a global or class-static variable, + // because a smart compiler might resolve it at compile time. + // + // On the other hand, we don't want to make a useless function call too + // often for performance reasons. A local static variable is initialised + // only on the definition's first execution. Compilers will be well + // optimised for this behaviour, so there's a minimal one-time cost. + static auto const version_ok{internal::PQXX_VERSION_CHECK()}; + ignore_unused(version_ok); +} + + +/// Descriptor of library's thread-safety model. +/** This describes what the library knows about various risks to thread-safety. + */ +struct PQXX_LIBEXPORT thread_safety_model +{ + /// Is the underlying libpq build thread-safe? + bool safe_libpq = false; + + /// Is Kerberos thread-safe? + /** @warning Is currently always `false`. + * + * If your application uses Kerberos, all accesses to libpqxx or Kerberos + * must be serialized. Confine their use to a single thread, or protect it + * with a global lock. + */ + bool safe_kerberos = false; + + /// A human-readable description of any thread-safety issues. + std::string description; +}; + + +/// Describe thread safety available in this build. +[[nodiscard]] PQXX_LIBEXPORT thread_safety_model describe_thread_safety(); + + +#if defined(PQXX_HAVE_CONCEPTS) +# define PQXX_POTENTIAL_BINARY_ARG pqxx::potential_binary +#else +# define PQXX_POTENTIAL_BINARY_ARG typename +#endif + + +/// Cast binary data to a type that libpqxx will recognise as binary. +/** There are many different formats for storing binary data in memory. You + * may have yours as a `std::string`, or a `std::vector`, or one of + * many other types. + * + * But for libpqxx to recognise your data as binary, it needs to be a + * `std::basic_string`, or a `std::basic_string_view`; + * or in C++20 or better, any contiguous block of `std::byte`. + * + * Use `binary_cast` as a convenience helper to cast your data as a + * `std::basic_string_view`. + * + * @warning There are two things you should be aware of! First, the data must + * be contiguous in memory. In C++20 the compiler will enforce this, but in + * C++17 it's your own problem. Second, you must keep the object where you + * store the actual data alive for as long as you might use this function's + * return value. + */ +template +std::basic_string_view binary_cast(TYPE const &data) +{ + static_assert(sizeof(value_type) == 1); + return { + reinterpret_cast( + const_cast const *>( + std::data(data))), + std::size(data)}; +} + + +#if defined(PQXX_HAVE_CONCEPTS) +template +concept char_sized = (sizeof(CHAR) == 1); +# define PQXX_CHAR_SIZED_ARG char_sized +#else +# define PQXX_CHAR_SIZED_ARG typename +#endif + +/// Construct a type that libpqxx will recognise as binary. +/** Takes a data pointer and a size, without being too strict about their + * types, and constructs a `std::basic_string_view` pointing to + * the same data. + * + * This makes it a little easier to turn binary data, in whatever form you + * happen to have it, into binary data as libpqxx understands it. + */ +template +std::basic_string_view binary_cast(CHAR const *data, SIZE size) +{ + static_assert(sizeof(CHAR) == 1); + return { + reinterpret_cast(data), + check_cast(size, "binary data size")}; +} + + +/// The "null" oid. +constexpr oid oid_none{0}; +} // namespace pqxx + + +/// Private namespace for libpqxx's internal use; do not access. +/** This namespace hides definitions internal to libpqxx. These are not + * supposed to be used by client programs, and they may change at any time + * without notice. + * + * Conversely, if you find something in this namespace tremendously useful, by + * all means do lodge a request for its publication. + * + * @warning Here be dragons! + */ +namespace pqxx::internal +{ +using namespace std::literals; + + +/// A safer and more generic replacement for `std::isdigit`. +/** Turns out `std::isdigit` isn't as easy to use as it sounds. It takes an + * `int`, but requires it to be nonnegative. Which means it's an outright + * liability on systems where `char` is signed. + */ +template inline constexpr bool is_digit(CHAR c) noexcept +{ + return (c >= '0') and (c <= '9'); +} + + +/// Describe an object for humans, based on class name and optional name. +/** Interprets an empty name as "no name given." + */ +[[nodiscard]] std::string +describe_object(std::string_view class_name, std::string_view name); + + +/// Check validity of registering a new "guest" in a "host." +/** The host might be e.g. a connection, and the guest a transaction. The + * host can only have one guest at a time, so it is an error to register a new + * guest while the host already has a guest. + * + * If the new registration is an error, this function throws a descriptive + * exception. + * + * Pass the old guest (if any) and the new guest (if any), for both, a type + * name (at least if the guest is not null), and optionally an object name + * (but which may be omitted if the caller did not assign one). + */ +void check_unique_register( + void const *old_guest, std::string_view old_class, std::string_view old_name, + void const *new_guest, std::string_view new_class, + std::string_view new_name); + + +/// Like @ref check_unique_register, but for un-registering a guest. +/** Pass the guest which was registered, as well as the guest which is being + * unregistered, so that the function can check that they are the same one. + */ +void check_unique_unregister( + void const *old_guest, std::string_view old_class, std::string_view old_name, + void const *new_guest, std::string_view new_class, + std::string_view new_name); + + +/// Compute buffer size needed to escape binary data for use as a BYTEA. +/** This uses the hex-escaping format. The return value includes room for the + * "\x" prefix. + */ +inline constexpr std::size_t size_esc_bin(std::size_t binary_bytes) noexcept +{ + return 2 + (2 * binary_bytes) + 1; +} + + +/// Compute binary size from the size of its escaped version. +/** Do not include a terminating zero in `escaped_bytes`. + */ +inline constexpr std::size_t size_unesc_bin(std::size_t escaped_bytes) noexcept +{ + return (escaped_bytes - 2) / 2; +} + + +// TODO: Use actual binary type for "data". +/// Hex-escape binary data into a buffer. +/** The buffer must be able to accommodate + * `size_esc_bin(std::size(binary_data))` bytes, and the function will write + * exactly that number of bytes into the buffer. This includes a trailing + * zero. + */ +void PQXX_LIBEXPORT +esc_bin(std::basic_string_view binary_data, char buffer[]) noexcept; + + +/// Hex-escape binary data into a std::string. +std::string PQXX_LIBEXPORT +esc_bin(std::basic_string_view binary_data); + + +/// Reconstitute binary data from its escaped version. +void PQXX_LIBEXPORT +unesc_bin(std::string_view escaped_data, std::byte buffer[]); + + +/// Reconstitute binary data from its escaped version. +std::basic_string + PQXX_LIBEXPORT unesc_bin(std::string_view escaped_data); + + +/// Transitional: std::ssize(), or custom implementation if not available. +template auto ssize(T const &c) +{ +#if defined(__cpp_lib_ssize) && __cplusplus >= __cpp_lib_ssize + return std::ssize(c); +#else + using signed_t = std::make_signed_t; + return static_cast(std::size(c)); +#endif // __cpp_lib_ssize +} + + +/// Helper for determining a function's parameter types. +/** This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +std::tuple args_f(RETURN (&func)(ARGS...)); + + +/// Helper for determining a `std::function`'s parameter types. +/** This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +std::tuple args_f(std::function const &); + + +/// Helper for determining a member function's parameter types. +/** This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +std::tuple member_args_f(RETURN (CLASS::*)(ARGS...)); + + +/// Helper for determining a const member function's parameter types. +/** This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +std::tuple member_args_f(RETURN (CLASS::*)(ARGS...) const); + + +/// Helper for determining a callable type's parameter types. +/** This specialisation should work for lambdas. + * + * This function has no definition. It's not meant to be actually called. + * It's just there for pattern-matching in the compiler, so we can use its + * hypothetical return value. + */ +template +auto args_f(CALLABLE const &f) + -> decltype(member_args_f(&CALLABLE::operator())); + + +/// A callable's parameter types, as a tuple. +template +using args_t = decltype(args_f(std::declval())); + + +/// Helper: Apply `strip_t` to each of a tuple type's component types. +/** This function has no definition. It is not meant to be called, only to be + * used to deduce the right types. + */ +template +std::tuple...> strip_types(std::tuple const &); + + +/// Take a tuple type and apply @ref strip_t to its component types. +template +using strip_types_t = decltype(strip_types(std::declval())); +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version new file mode 100644 index 000000000..8dd5e48d4 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version @@ -0,0 +1,7 @@ +/** libpqxx version info. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/version.hxx" +#include "pqxx/internal/header-post.hxx" + diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version.hxx new file mode 100644 index 000000000..a159f1bed --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/version.hxx @@ -0,0 +1,55 @@ +/* Version info for libpqxx. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/version instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_VERSION + +# if !defined(PQXX_HEADER_PRE) +# error "Include libpqxx headers as , not ." +# endif + +/// Full libpqxx version string. +# define PQXX_VERSION "7.7.3" +/// Library ABI version. +# define PQXX_ABI "7.7" + +/// Major version number. +# define PQXX_VERSION_MAJOR 7 +/// Minor version number. +# define PQXX_VERSION_MINOR 7 + +# define PQXX_VERSION_CHECK check_pqxx_version_7_7 + +namespace pqxx::internal +{ +/// Library version check stub. +/** Helps detect version mismatches between libpqxx headers and the libpqxx + * library binary. + * + * Sometimes users run into trouble linking their code against libpqxx because + * they build their own libpqxx, but the system also has a different version + * installed. The declarations in the headers against which they compile their + * code will differ from the ones used to build the libpqxx version they're + * using, leading to confusing link errors. The solution is to generate a link + * error when the libpqxx binary is not the same version as the libpqxx headers + * used to compile the code. + * + * This function's definition is in the libpqxx binary, so it's based on the + * version as found in the binary. The headers contain a call to the function, + * whose name contains the libpqxx version as found in the headers. (The + * library build process will use its own local headers even if another version + * of the headers is installed on the system.) + * + * If the libpqxx binary was compiled for a different version than the user's + * code, linking will fail with an error: `check_pqxx_version_*_*` will not + * exist for the given version number. + */ +PQXX_LIBEXPORT int PQXX_VERSION_CHECK() noexcept; +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview new file mode 100644 index 000000000..66ea2a625 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview @@ -0,0 +1,6 @@ +/** Zero-terminated string view class. + */ +// Actual definitions in .hxx file so editors and such recognize file type. +#include "pqxx/internal/header-pre.hxx" +#include "pqxx/zview.hxx" +#include "pqxx/internal/header-post.hxx" diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview.hxx b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview.hxx new file mode 100644 index 000000000..36a779f51 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/include/pqxx/zview.hxx @@ -0,0 +1,163 @@ +/* Zero-terminated string view. + * + * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/zview instead. + * + * Copyright (c) 2000-2022, Jeroen T. Vermeulen. + * + * See COPYING for copyright license. If you did not receive a file called + * COPYING with this source code, please notify the distributor of this + * mistake, or contact the author. + */ +#ifndef PQXX_H_ZVIEW +#define PQXX_H_ZVIEW + +#include +#include +#include + +#include "pqxx/types.hxx" + + +namespace pqxx +{ +/// Marker-type wrapper: zero-terminated `std::string_view`. +/** @warning Use this only if the underlying string is zero-terminated. + * + * When you construct a zview, you are promising that if the data pointer is + * non-null, the underlying string is zero-terminated. It otherwise behaves + * exactly like a std::string_view. + * + * The terminating zero is not "in" the string, so it does not count as part of + * the view's length. + * + * The added guarantee lets the view be used as a C-style string, which often + * matters since libpqxx builds on top of a C library. For this reason, zview + * also adds a @ref c_str method. + */ +class zview : public std::string_view +{ +public: + constexpr zview() noexcept = default; + + /// Convenience overload: construct using pointer and signed length. + constexpr zview(char const text[], std::ptrdiff_t len) : + std::string_view{text, static_cast(len)} + {} + + /// Convenience overload: construct using pointer and signed length. + constexpr zview(char text[], std::ptrdiff_t len) : + std::string_view{text, static_cast(len)} + {} + + /// Explicitly promote a `string_view` to a `zview`. + explicit constexpr zview(std::string_view other) noexcept : + std::string_view{other} + {} + + /// Construct from any initialiser you might use for `std::string_view`. + /** @warning Only do this if you are sure that the string is zero-terminated. + */ + template + explicit constexpr zview(Args &&...args) : + std::string_view(std::forward(args)...) + {} + + // C++20: constexpr. + /// @warning There's an implicit conversion from `std::string`. + zview(std::string const &str) noexcept : + std::string_view{str.c_str(), str.size()} + {} + + /// Construct a `zview` from a C-style string. + /** @warning This scans the string to discover its length. So if you need to + * do it many times, it's probably better to create the `zview` once and + * re-use it. + */ + constexpr zview(char const str[]) : std::string_view{str} {} + + /// Construct a `zview` from a string literal. + /** A C++ string literal ("foo") normally looks a lot like a pointer to + * char const, but that's not really true. It's actually an array of char, + * which _devolves_ to a pointer when you pass it. + * + * For the purpose of creating a `zview` there is one big difference: if we + * know the array's size, we don't need to scan through the string in order + * to find out its length. + */ + template + constexpr zview(char const (&literal)[size]) : zview(literal, size - 1) + {} + + /// Either a null pointer, or a zero-terminated text buffer. + [[nodiscard]] constexpr char const *c_str() const &noexcept + { + return data(); + } +}; + + +/// Support @ref zview literals. +/** You can "import" this selectively into your namespace, without pulling in + * all of the @ref pqxx namespace: + * + * ```cxx + * using pqxx::operator"" _zv; + * ``` + */ +constexpr zview operator"" _zv(char const str[], std::size_t len) noexcept +{ + return zview{str, len}; +} +} // namespace pqxx + + +#if defined(PQXX_HAVE_CONCEPTS) +/// A zview is a view. +template<> inline constexpr bool std::ranges::enable_view{true}; + + +/// A zview is a borrowed range. +template<> +inline constexpr bool std::ranges::enable_borrowed_range{true}; + +namespace pqxx::internal +{ +/// Concept: T is a known zero-terminated string type. +/** There's no unified API for these string types. It's just a check for some + * known types. Any code that makes use of the concept will still have to + * support each of these individually. + */ +template +concept ZString = std::is_convertible_v < strip_t, +char const * > or std::is_convertible_v, zview> or + std::is_convertible_v; +} // namespace pqxx::internal +#endif // PQXX_HAVE_CONCEPTS + + +namespace pqxx::internal +{ +/// Get a raw C string pointer. +inline constexpr char const *as_c_string(char const str[]) noexcept +{ + return str; +} +/// Get a raw C string pointer. +template +inline constexpr char const *as_c_string(char (&str)[N]) noexcept +{ + return str; +} +/// Get a raw C string pointer. +inline constexpr char const *as_c_string(pqxx::zview str) noexcept +{ + return str.c_str(); +} +// C++20: Make this constexpr. +/// Get a raw C string pointer. +inline char const *as_c_string(std::string const &str) noexcept +{ + return str.c_str(); +} +} // namespace pqxx::internal +#endif diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config-version.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config-version.cmake new file mode 100644 index 000000000..c47d6956d --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config-version.cmake @@ -0,0 +1,70 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version, +# but only if the requested major version is the same as the current one. +# The variable CVF_VERSION must be set before calling configure_file(). + + +set(PACKAGE_VERSION "7.7.3") + +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + + if("7.7.3" MATCHES "^([0-9]+)\\.") + set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}") + if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}") + endif() + else() + set(CVF_VERSION_MAJOR "7.7.3") + endif() + + if(PACKAGE_FIND_VERSION_RANGE) + # both endpoints of the range must have the expected major version + math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1") + if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT))) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX))) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + else() + if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() + endif() +endif() + + +# if the installed project requested no architecture check, don't perform the check +if("FALSE") + return() +endif() + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config.cmake new file mode 100644 index 000000000..cb25a05f2 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-config.cmake @@ -0,0 +1,4 @@ +include(CMakeFindDependencyMacro) +find_dependency(PostgreSQL) + +include("${CMAKE_CURRENT_LIST_DIR}/libpqxx-targets.cmake") diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake new file mode 100644 index 000000000..980f46098 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets-noconfig.cmake @@ -0,0 +1,19 @@ +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "libpqxx::pqxx" for configuration "" +set_property(TARGET libpqxx::pqxx APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG) +set_target_properties(libpqxx::pqxx PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_NOCONFIG "CXX" + IMPORTED_LOCATION_NOCONFIG "${_IMPORT_PREFIX}/lib/libpqxx-7.7.a" + ) + +list(APPEND _IMPORT_CHECK_TARGETS libpqxx::pqxx ) +list(APPEND _IMPORT_CHECK_FILES_FOR_libpqxx::pqxx "${_IMPORT_PREFIX}/lib/libpqxx-7.7.a" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets.cmake b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets.cmake new file mode 100644 index 000000000..c7b525b18 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/cmake/libpqxx/libpqxx-targets.cmake @@ -0,0 +1,99 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.6) + message(FATAL_ERROR "CMake >= 2.6.0 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.6...3.20) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_targetsDefined) +set(_targetsNotDefined) +set(_expectedTargets) +foreach(_expectedTarget libpqxx::pqxx) + list(APPEND _expectedTargets ${_expectedTarget}) + if(NOT TARGET ${_expectedTarget}) + list(APPEND _targetsNotDefined ${_expectedTarget}) + endif() + if(TARGET ${_expectedTarget}) + list(APPEND _targetsDefined ${_expectedTarget}) + endif() +endforeach() +if("${_targetsDefined}" STREQUAL "${_expectedTargets}") + unset(_targetsDefined) + unset(_targetsNotDefined) + unset(_expectedTargets) + set(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT "${_targetsDefined}" STREQUAL "") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") +endif() +unset(_targetsDefined) +unset(_targetsNotDefined) +unset(_expectedTargets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target libpqxx::pqxx +add_library(libpqxx::pqxx STATIC IMPORTED) + +set_target_properties(libpqxx::pqxx PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" + INTERFACE_LINK_LIBRARIES "/usr/lib/aarch64-linux-gnu/libpq.so" +) + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "This file relies on consumers using CMake 2.8.12 or greater.") +endif() + +# Load information for each installed configuration. +get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(GLOB CONFIG_FILES "${_DIR}/libpqxx-targets-*.cmake") +foreach(f ${CONFIG_FILES}) + include(${f}) +endforeach() + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(target ${_IMPORT_CHECK_TARGETS} ) + foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} ) + if(NOT EXISTS "${file}" ) + message(FATAL_ERROR "The imported target \"${target}\" references the file + \"${file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + unset(_IMPORT_CHECK_FILES_FOR_${target}) +endforeach() +unset(_IMPORT_CHECK_TARGETS) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx-7.7.a b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx-7.7.a new file mode 100644 index 000000000..2cb705a34 Binary files /dev/null and b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx-7.7.a differ diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx.a b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx.a new file mode 120000 index 000000000..d9fcdab85 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/lib/libpqxx.a @@ -0,0 +1 @@ +libpqxx-7.7.a \ No newline at end of file diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/accessing-results.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/accessing-results.md new file mode 100644 index 000000000..920fb6f3b --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/accessing-results.md @@ -0,0 +1,157 @@ +Accessing results and result rows {#accessing-results} +--------------------------------- + +When you execute a query using one of the transaction `exec` functions, you +normally get a `result` object back. A `result` is a container of `row`s. + +(There are exceptions. The `exec1` functions expect exactly one row of data, +so they return just a `row`, not a full `result`.) + +Result objects are an all-or-nothing affair. The `exec` function waits until +it's received all the result data, and then gives it to you in the form of the +`result`. _(There is a faster, easier way of executing simple queries, so see +"streaming rows" below as well.)_ + +For example, your code might do: + +```cxx + pqxx::result r = tx.exec("SELECT * FROM mytable"); +``` + +Now, how do you access the data inside `r`? + +Result sets act as standard C++ containers of rows. Rows act as standard +C++ containers of fields. So the easiest way to go through them is: + +```cxx + for (auto const &row: r) + { + for (auto const &field: row) std::cout << field.c_str() << '\t'; + std::cout << '\n'; + } +``` + +But results and rows also support other kinds of access. Array-style +indexing, for instance, such as `r[rownum]`: + +```cxx + std::size_t const num_rows = std::size(r); + for (std::size_t rownum=0u; rownum < num_rows; ++rownum) + { + pqxx::row const row = r[rownum]; + std::size_t const num_cols = std::size(row); + for (std::size_t colnum=0u; colnum < num_cols; ++colnum) + { + pqxx::field const field = row[colnum]; + std::cout << field.c_str() << '\t'; + } + + std::cout << '\n'; + } +``` + +Every row in the result has the same number of columns, so you don't need to +look up the number of fields again for each one: + +```cxx + std::size_t const num_rows = std::size(r); + std::size_t const num_cols = r.columns(); + for (std::size_t rownum=0u; rownum < num_rows; ++rownum) + { + pqxx::row const row = r[rownum]; + for (std::size_t colnum=0u; colnum < num_cols; ++colnum) + { + pqxx::field const field = row[colnum]; + std::cout << field.c_str() << '\t'; + } + + std::cout << '\n'; + } +``` + +You can even address a field by indexing the `row` using the field's _name:_ + +```cxx + std::cout << row["salary"] << '\n'; +``` + +But try not to do that if speed matters, because looking up the column by name +takes time. At least you'd want to look up the column index before your loop +and then use numerical indexes inside the loop. + +For C++23 or better, there's also a two-dimensional array access operator: + +```cxx + for (std::size_t rownum=0u; rownum < num_rows; ++rownum) + { + for (std::size_t colnum=0u; colnum < num_cols; ++colnum) + std::cout result[rownum, colnum].c_str() << '\t'; + std::cout << '\n'; + } +``` + +And of course you can use classic "begin/end" loops: + +```cxx + for (auto row = std::begin(r); row != std::end(r); row++) + { + for (auto field = std::begin(row); field != std::end(row); field++) + std::cout << field->c_str() << '\t'; + std::cout << '\n'; + } +``` + +Result sets are immutable, so all iterators on results and rows are actually +`const_iterator`s. There are also `const_reverse_iterator` types, which +iterate backwards from `rbegin()` to `rend()` exclusive. + +All these iterator types provide one extra bit of convenience that you won't +normally find in C++ iterators: referential transparency. You don't need to +dereference them to get to the row or field they refer to. That is, instead +of `row->end()` you can also choose to say `row.end()`. Similarly, you +may prefer `field.c_str()` over `field->c_str()`. + +This becomes really helpful with the array-indexing operator. With regular +C++ iterators you would need ugly expressions like `(*row)[0]` or +`row->operator[](0)`. With the iterator types defined by the result and +row classes you can simply say `row[0]`. + + +Streaming rows +-------------- + +There's another way to go through the rows coming out of a query. It's +usually easier and faster, but there are drawbacks. + +**One,** you start getting rows before all the data has come in from the +database. That speeds things up, but what happens if you lose your network +connection while transferring the data? Your application may already have +processed some of the data before finding out that the rest isn't coming. If +that is a problem for your application, streaming may not be the right choice. + +**Two,** streaming only works for some types of query. The `stream()` function +wraps your query in a PostgreSQL `COPY` command, and `COPY` only supports a few +commands: `SELECT`, `VALUES`, `or an `INSERT`, `UPDATE`, or `DELETE` with a +`RETURNING` clause. See the `COPY` documentation here: +https://www.postgresql.org/docs/current/sql-copy.html + +**Three,** when you convert a field to a "view" type (such as +`std::string_view` or `std::basic_string_view`), the view points to +underlying data which only stays valid until you iterate to the next row or +exit the loop. So if you want to use that data for longer than a single +iteration of the streaming loop, you'll have to store it somewhere yourself. + +Now for the good news. Streaming does make it very easy to query data and loop +over it: + +```cxx + for (auto [id, name, x, y] : + tx.stream( + "SELECT id, name, x, y FROM point")) + process(id + 1, "point-" + name, x * 10.0, y * 10.0); +``` + +The conversion to C++ types (here `int`, `std::string_view`, and two `float`s) +is built into the function. You never even see `row` objects, `field` objects, +iterators, or conversion methods. You just put in your query and you receive +your data. diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/binary-data.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/binary-data.md new file mode 100644 index 000000000..20da8dc0c --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/binary-data.md @@ -0,0 +1,56 @@ +Binary data {#binary} +=========== + +The database has two ways of storing binary data: `BYTEA` is like a string, but +containing bytes rather than text characters. And _large objects_ are more +like a separate table containing binary objects. + +Generally you'll want to use `BYTEA` for reasonably-sized values, and large +objects for very large values. + +That's the database side. On the C++ side, in libpqxx, all binary data must be +either `std::basic_string` or `std::basic_string_view`; +or if you're building in C++20 or better, anything that's a block of +contiguous `std::byte` in memory. + +So for example, if you want to write a large object, you'd create a +`pqxx::blob` object. And you might use that to write data in the form of +`std::basic_string_view`. + +Your particular binary data may look different though. You may have it in a +`std::string`, or a `std::vector`, or a pointer to `char` +accompanied by a size (which could be signed or unsigned, and of any of a few +different widths). Sometimes that's your choice, or sometimes some other +library will dictate what form it takes. + +So long as it's _basically_ still a block of bytes though, you can use +`pqxx::binary_cast` to construct a `std::basic_string_view` from it. + +There are two forms of `binary_cast`. One takes a single argument that must +support `std::data()` and `std::size()`: + + std::string hi{"Hello binary world"}; + my_blob.write(pqxx::binary_cast(hi); + +The other takes a pointer and a size: + + char const greeting[] = "Hello binary world"; + char const *hi = greeting; + my_blob.write(pqxx::binary_cast(hi, sizeof(greeting))); + + +Caveats +------- + +There are some restrictions on `binary_cast` that you must be aware of. + +First, your data must of a type that gives us _bytes._ So: `char`, +`unsigned char`, `signed char`, `int8_t`, `uint8_t`, or of course `std::byte`. +You can't feed in a vector of `double`, or anything like that. + +Second, the data must be laid out as a contiguous block in memory. If there's +no `std::data()` implementation for your type, it's not suitable. + +Third, `binary_cast` only constructs something like a `std::string_view`. It +does not make a copy of your actual data. So, make sure that your data remains +alive and in the same place while you're using it. diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/datatypes.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/datatypes.md new file mode 100644 index 000000000..bc14c8b90 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/datatypes.md @@ -0,0 +1,373 @@ +Supporting additional data types {#datatypes} +================================ + +Communication with the database mostly happens in a text format. When you +include an integer value in a query, you use `to_string` to convert it to that +text format. When you get a query result field "as a float," it converts from +the text format to a floating-point type. These conversions are everywhere in +libpqxx. + +The conversion sydstem supports many built-in types, but it is also extensible. +You can "teach" libpqxx (in the scope of your own application) to convert +additional types of values to and from PostgreSQL's string format. + +This is massively useful, but it's not for the faint of heart. You'll need to +specialise some templates. And, **the API for doing this can change with any +major libpqxx release.** + + +Converting types +---------------- + +In your application, a conversion is driven entirely by a C++ type you specify. +The value's SQL type has nothing to do with it, nor is there anything in the +string that would identify its type. + +So, if you've SELECTed a 64-bit integer from the database, and you try to +convert it to a C++ "short," one of two things will happen: either the number +is small enough to fit in your `short` (and it just works), or else it throws a +conversion exception. + +Or, your database table might have a text column, but a given field may contain +a string that _looks_ just like a number. You can convert that value to an +integer type just fine. Or to a floating-point type. All that matters to the +conversion is the actual value, and the type. + +In some cases the templates for these conversions can tell the type from the +arguments you pass them: + + auto x = to_string(99); + +In other cases you may need to instantiate template explicitly: + + auto y = from_string("99"); + + +Supporting a new type +--------------------- + +Let's say you have some other SQL type which you want to be able to store in, +or retrieve from, the database. What would it take to support that? + +Sometimes you do not need _complete_ support. You might need a conversion _to_ +a string but not _from_ a string, for example. The conversion is defined at +compile time, so don't be too afraid to be incomplete. If you leave out one of +these steps, it's not going to crash at run time or mess up your data. The +worst that can happen is that your code won't build. + +So what do you need for a complete conversion? + +First off, of course, you need a C++ type. It may be your own, but it +doesn't have to be. It could be a type from a third-party library, or even one +from the standard library that libpqxx does not yet support. + +You also specialise the `pqxx::type_name` variable to specify the type's name. +This is important for all code which mentions your type in human-readable text, +such as error messages. + +Then, does your type have a built-in null value? You specialise the +`pqxx::nullness` template to specify the details. + +Finally, you specialise the `pqxx::string_traits` template. This is where you +define the actual conversions. + +Let's go through these steps one by one. + + +Your type +--------- + +You'll need a type for which the conversions are not yet defined, because the +C++ type is what determines the right conversion. One type, one set of +conversions. + +The type doesn't have to be one that you create. The conversion logic was +designed such that you can build it around any type. So you can just as +easily build a conversion for a type that's defined somewhere else. There's +no need to include any special methods or other members inside it. That's also +how libpqxx can support converting built-in types like `int`. + +By the way, if the type is an enum, you don't need to do any of this. Just +invoke the preprocessor macro `PQXX_DECLARE_ENUM_CONVERSION`, from the global +namespace near the top of your translation unit, and pass the type as an +argument. + +The library also provides specialisations for `std::optional`, +`std::shared_ptr`, and `std::unique_ptr`. If you have conversions for +`T`, you'll also have conversions for those. + + +Specialise `type_name` +---------------------- + +When errors happen during conversion, libpqxx will compose error messages for +the user. Sometimes these will include the name of the type that's being +converted. + +To tell libpqxx the name of each type, there's a template variable called +`pqxx::type_name`. For any given type `T`, it should have a specialisation +that provides that `T`'s human-readable name: + + namespace pqxx + { + template<> std::string const type_name{"T"}; + } + +(Yes, this means that you need to define something inside the pqxx namespace. +Future versions of libpqxx may move this into a separate namespace.) + +Define this early on in your translation unit, before any code that might cause +libpqxx to need the name. That way, the libpqxx code which needs to know the +type's name can see your definition. + + +Specialise `nullness` +--------------------- + +A struct template `pqxx::nullness` defines whether your type has a natural +"null value" built in. If so, it also provides member functions for producing +and recognising null values. + +The simplest scenario is also the most common: most types don't have a null +value built in. In that case, derive your nullness traits from +`pqxx::no_null`: + + namespace pqxx + { + template<> struct nullness : pqxx::no_null {}; + } + +(Here again you're defining this in the pqxx namespace.) + +If your type does have a natural null value, the definition gets a little more +complex: + + namespace pqxx + { + template<> struct nullness + { + static constexpr bool has_null{true}; + static constexpr bool always_null{false}; + + static bool is_null(T const &value) + { + // Return whether "value" is null. + return ...; + } + + [[nodiscard]] static T null() + { + // Return a null value. + return ...; + } + }; + } + +You may be wondering why there's a function to produce a null value, but also a +function to check whether a value is null. Why not just compare the value to +the result of `null()`? Because two null values may not be equal. `T` may +have several different null values. Or it may override the comparison +operator, similar to SQL where NULL is not equal to NULL. + +As a third case, your type may be one that _always_ represents a null value. +This is the case for `std::nullptr_t` and `std::nullopt_t`. In that case, you +set `nullness::always_null` to `true` (as well as `has_null` of course), +and you won't need to define any actual conversions. + + +Specialise `string_traits` +------------------------- + +This part is more work. (You can skip it for types that are _always_ null, +but those will be rare.) Specialise the `pqxx::string_traits` template: + + namespace pqxx + { + template<> struct string_traits + { + static T from_string(std::string_view text); + static zview to_buf(char *begin, char *end, T const &value); + static char *into_buf(char *begin, char *end, T const &value); + static std::size_t size_buffer(T const &value) noexcept; + }; + } + +You'll also need to write those member functions, or as many of them as needed +to get your code to build. + + +### `from_string` + +We start off simple: `from_string` parses a string as a value of `T`, and +returns that value. + +The string may not be zero-terminated; it's just the `string_view` from +beginning to end (exclusive). In your tests, cover cases where the string +does not end in a zero byte. + +It's perfectly possible that the string isn't actually a `T` value. Mistakes +happen. In that case, throw a `pqxx::conversion_error`. + +(Of course it's also possible that you run into some other error, so it's fine +to throw different exceptions as well. But when it's definitely "this is not +the right format for a `T`," throw `conversion_error`.) + + +### `to_buf` + +In this function, you convert a value of `T` into a string that the postgres +server will understand. + +The caller will provide you with a buffer where you can write the string, if +you need it: from `begin` to `end` exclusive. It's a half-open interval, so +don't access `*end`. + +If the buffer is insufficient for you to do the conversion, throw a +`pqxx::conversion_overrun`. It doesn't have to be exact: you can be a little +pessimistic and demand a bit more space than you need. Just be sure to throw +the exception if there's any risk of overrunning the buffer. + +You don't _have_ to use the buffer for this function though. For example, +`pqxx::string_traits::to_buf` returns a compile-time constant string and +ignores the buffer. + +Even if you do use the buffer, your string does not _have_ to start at the +beginning of the buffer. For example, the integer conversions start by writing +the _least_ significant digit to the _end_ of the buffer, and then writes the +more significant digits before it. It was just more convenient. + +Return a `pqxx::zview`. This is basically a `std::string_view`, but with one +difference: a `zview` guarantees that there will be a valid zero byte right +after the `string_view`. The zero byte is not counted as part of its size, but +it will be there. + +Expressed in code, this rule must hold: + + void invariant(zview z) + { + assert(z[std::size(z)] == 0); + } + +Make sure you write your trailing zero _before_ the `end`. If the trailing +zero doesn't fit in the buffer, then there's just not enough room to perform +the conversion. + +Beware of locales when converting. If you use standard library features like +`sprintf`, they may obey whatever locale is currently set on the system. That +means that a simple integer like 1000000 may come out as "1000000" on your +system, but as "1,000,000" on mine, or as "1.000.000" for somebody else, and on +an Indian system it may be "1,00,000". Values coming from or going to the +database should be in non-localised formats. You can use libpqxx functions for +those conversions: `pqxx::from_string`, `pqxx::to_string`, `pqxx::to_buf`. + + +### `into_buf` + +This is a stricter version of `to_buf`. All the same requirements apply, but +in addition you must write your string into the buffer provided, starting +_exactly_ at `begin`. + +That's why this function returns just a simple pointer: the address right +behind the trailing zero. If the caller wants to use the string, they can +find it at `begin`. If they want to write a different value into the rest of +the buffer, they can start at the location you returned. + + +### `size_buffer` + +Here you estimate how much buffer space you need for converting a `T` to a +string. Be precise if you can, but pessimistic if you must. It's usually +better to waste a few unnecessary bytes than to spend a lot of time computing +the exact buffer space you need. And failing the conversion because you +under-budgeted the buffer is worst of all. + +Include the trailing zero in the buffer size. If your `to_buf` takes more +space than just what's needed to store the result, include that too. + +Make `size_buffer` a `constexpr` function if you can. It can allow the caller +to allocate the buffer on the stack, with a size known at compile time. + + +Optional: Specialise `is_unquoted_safe` +--------------------------------------- + +When converting arrays or composite values to strings, libpqxx may need to +quote values and escape any special characters. This takes time. + +Some types though, such as integral or floating-point types, can never have +any special characters such as quotes, commas, or backslashes in their string +representations. In such cases, there's no need to quote or escape such values +in arrays or composite types. + +If your type is like that, you can tell libpqxx about this by defining: + + namespace pqxx + { + template<> inline constexpr bool is_unquoted_safe{true}; + } + +The code that converts this type of field to strings in an array or a composite +type can then use a simpler, more efficient variant of the code. It's always +safe to leave this out; it's _just_ an optimisation for when you're completely +sure that it's safe. + +Do not do this if a string representation of your type may contain a comma; +semicolon; parenthesis; brace; quote; backslash; newline; or any other +character that might need escaping. + + +Optional: Specialise `param_format` +----------------------------------- + +This one you don't generally need to worry about. Read on if you're writing a +type which represents raw binary data, or if you're writing a template where +_some specialisations_ may contain raw binary data. + +When you call parameterised statements, or prepared statements with parameters, +libpqxx needs to your parameters on to libpq, the underlying C-level PostgreSQL +client library. + +There are two formats for doing that: _text_ and _binary._ In the first, we +represent all values as strings, and the server then converts them into its own +internal binary representation. That's what the string conversions are all +about, and it's what we do for almost all types of parameters. + +But we do it differently when the parameter is a contiguous series of raw bytes +and the corresponding SQL type is `BYTEA`. There is a text format for those, +but we bypass it for efficiency. The server can use the binary data in the +exact same form, without any conversion or extra processing. The binary data +is also twice as compact during transport. + +(People sometimes ask why we can't just treat all types as binary. However the +general case isn't so clear-cut. The binary formats are not documented, there +are no guarantees that they will be platform-independent or that they will +remain stable, and there's no really solid way to detect when we might get the +format wrong. But also, the conversions aren't necessarily as straightforward +and efficient as they sound. So, for the general case, libpqxx sticks with the +text formats. Raw binary data alone stands out as a clear win.) + +Long story short, the machinery for passing parameters needs to know: is this +parameter a binary string, or not? In the normal case it can assume "no," and +that's what it does. The text format is always a safe choice; we just try to +use the binary format where it's faster. + +The `param_format` function template is what makes the decision. We specialise +it for types which may be binary strings, and use the default for all other +types. + +"Types which _may_ be binary"? You might think we know whether a type is a +binary type or not. But there are some complications with generic types. + +Templates like `std::shared_ptr`, `std::optional`, and so on act like +"wrappers" for another type. A `std::optional` is binary if `T` is binary. +Otherwise, it's not. If you're building support for a template of this nature, +you'll probably want to implement `param_format` for it. + +The decision to use binary format is made based on a given object, not +necessarily based on the type in general. Look at `std::variant`. If you have +a `std::variant` type which can hold an `int` or a binary string, is that a +binary parameter? We can't decide without knowing the individual object. + +Containers are another hard case. Should we pass `std::vector` in binary? +Even when `T` is a binary type, we don't currently have any way to pass an +array in binary format, so we always pass it as text. diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/escaping.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/escaping.md new file mode 100644 index 000000000..2ad9fe3db --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/escaping.md @@ -0,0 +1,74 @@ +String escaping {#escaping} +=============== + +Writing queries as strings is easy. But sometimes you need a variable in +there: `"SELECT id FROM user WHERE name = '" + name + "'"`. + +This is dangerous. See the bug? If `name` can contain quotes, you may have +an SQL injection vulnerability there, where users can enter nasty stuff like +"`.'; DROP TABLE user`". Or if you're lucky, it's just a nasty bug that you +discover when `name` happens to be "d'Arcy". + +So, you'll need to _escape_ the `name` before you insert it. This is where +quotes and other problematic characters are marked as "this is just a character +in the string, not the end of the string." There are +[several functions](@ref escaping-functions) in libpqxx to do this for you. + + +SQL injection +------------- + +To understand what SQL injection vulnerabilities are and why they should be +prevented, imagine you use the following SQL statement somewhere in your +program: + + TX.exec( + "SELECT number,amount " + "FROM accounts " + "WHERE allowed_to_see('" + userid + "','" + password + "')"); + +This shows a logged-in user important information on all accounts he is +authorized to view. The userid and password strings are variables entered +by the user himself. + +Now, if the user is actually an attacker who knows (or can guess) the +general shape of this SQL statement, imagine he enters the following +password: + + x') OR ('x' = 'x + +Does that make sense to you? Probably not. But if this is inserted into +the SQL string by the C++ code above, the query becomes: + + SELECT number,amount + FROM accounts + WHERE allowed_to_see('user','x') OR ('x' = 'x') + +Is this what you wanted to happen? Probably not! The neat `allowed_to_see()` +clause is completely circumvented by the "`OR ('x' = 'x')`" clause, which is +always `true`. Therefore, the attacker will get to see all accounts in the +database! + + +Using the esc functions +----------------------- + +Here's how you can fix the problem in the example above: + + TX.exec( + "SELECT number,amount " + "FROM accounts " + "WHERE allowed_to_see('" + TX.esc(userid) + "', " + "'" + TX.esc(password) + "')"); + +Now, the quotes embedded in the attacker's string will be neatly escaped so +they can't "break out" of the quoted SQL string they were meant to go into: + + SELECT number,amount + FROM accounts + WHERE allowed_to_see('user', 'x'') OR (''x'' = ''x') + +If you look carefully, you'll see that thanks to the added escape characters +(a single-quote is escaped in SQL by doubling it) all we get is a very +strange-looking password string--but not a change in the SQL statement. + diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/getting-started.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/getting-started.md new file mode 100644 index 000000000..1b87b881f --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/getting-started.md @@ -0,0 +1,142 @@ +Getting started {#getting-started} +=============== + +The most basic three types in libpqxx are the _connection_, the _transaction_, +and the _result_. + +They fit together as follows: +* You connect to the database by creating a `pqxx::connection` object (see + @ref connections). + +* You create a transaction object (see @ref transactions) operating on that + connection. You'll usually want the `pqxx::work` variety. + + Once you're done you call the transaction's `commit` function to make its + work final. If you don't call this, the work will be rolled back when the + transaction object is destroyed. + +* Until then, use the transaction's `exec`, `query_value`, and `stream` + functions (and variants) to execute SQL statements. You pass the statements + themselves in as simple strings. (See @ref streams for more about data + streaming). + +* Most of the `exec` functions return a `pqxx::result` object, which acts + as a standard container of rows: `pqxx::row`. + + Each row in a result, in turn, acts as a container of fields: `pqxx::field`. + See @ref accessing-results for more about results, rows, and fields. + +* Each field's data is stored internally as a text string, in a format defined + by PostgreSQL. You can convert field or row values using their `as()` and + `to()` member functions. + +* After you've closed the transaction, the connection is free to run a next + transaction. + +Here's a very basic example. It connects to the default database (you'll +need to have one set up), queries it for a very simple result, converts it to +an `int`, and prints it out. It also contains some basic error handling. + + #include + #include + + int main() + { + try + { + // Connect to the database. In practice we may have to pass some + // arguments to say where the database server is, and so on. + // The constructor parses options exactly like libpq's + // PQconnectdb/PQconnect, see: + // https://www.postgresql.org/docs/10/static/libpq-connect.html + pqxx::connection c; + + // Start a transaction. In libpqxx, you always work in one. + pqxx::work w(c); + + // work::exec1() executes a query returning a single row of data. + // We'll just ask the database to return the number 1 to us. + pqxx::row r = w.exec1("SELECT 1"); + + // Commit your transaction. If an exception occurred before this + // point, execution will have left the block, and the transaction will + // have been destroyed along the way. In that case, the failed + // transaction would implicitly abort instead of getting to this point. + w.commit(); + + // Look at the first and only field in the row, parse it as an integer, + // and print it. + // + // "r[0]" returns the first field, which has an "as<...>()" member + // function template to convert its contents from their string format + // to a type of your choice. + std::cout << r[0].as() << std::endl; + } + catch (std::exception const &e) + { + std::cerr << e.what() << std::endl; + return 1; + } + } + +This prints the number 1. Notice that you can keep the result object around +after you've closed the transaction or even the connection. There are +situations where you can't do it, but generally it's fine. If you're +interested: you can install your own callbacks for receiving error messages +from the database, and in that case you'll have to keep the connection object +alive. But otherwise, it's nice to be able to "fire and forget" your +connection and deal with the data. + +You can also convert an entire row to a series of C++-side types in one go, +using the @c as member function on the row: + + pqxx::connection c; + pqxx::work w(c); + pqxx::row r = w.exec1("SELECT 1, 2, 'Hello'"); + auto [one, two, hello] = r.as(); + std::cout << (one + two) << ' ' << std::strlen(hello) << std::endl; + +Here's a slightly more complicated example. It takes an argument from the +command line and retrieves a string with that value. The interesting part is +that it uses the escaping-and-quoting function `quote` to embed this +string value in SQL safely. It also reads the result field's value as a +plain C-style string using its `c_str` function. + + #include + #include + #include + + int main(int argc, char *argv[]) + { + try + { + if (!argv[1]) throw std::runtime_error("Give me a string!"); + + pqxx::connection c; + pqxx::work w(c); + + // work::exec() returns a full result set, which can consist of any + // number of rows. + pqxx::result r = w.exec("SELECT " + w.quote(argv[1])); + + // End our transaction here. We can still use the result afterwards. + w.commit(); + + // Print the first field of the first row. Read it as a C string, + // just like std::string::c_str() does. + std::cout << r[0][0].c_str() << std::endl; + } + catch (std::exception const &e) + { + std::cerr << e.what() << std::endl; + return 1; + } + } + +You can find more about converting field values to native types, or +converting values to strings for use with libpqxx, under +@ref stringconversion. More about getting to the rows and fields of a +result is under @ref accessing-results. + +If you want to handle exceptions thrown by libpqxx in more detail, for +example to print the SQL contents of a query that failed, see @ref exception. diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/mainpage.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/mainpage.md new file mode 100644 index 000000000..5d4b8f9b2 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/mainpage.md @@ -0,0 +1,28 @@ +libpqxx {#mainpage} +======= + +@version 7.7.3 +@author Jeroen T. Vermeulen +@see http://pqxx.org +@see https://github.com/jtv/libpqxx + +Welcome to libpqxx, the C++ API to the PostgreSQL database management system. + +Compiling this package requires PostgreSQL to be installed -- including the +C headers for client development. The library builds on top of PostgreSQL's +standard C API, libpq. The libpq headers are not needed to compile client +programs, however. + +For a quick introduction to installing and using libpqxx, see the README.md +file. The latest information can be found at http://pqxx.org/ + + +Some links that should help you find your bearings: +* @ref getting-started +* @ref thread-safety +* @ref connections +* @ref transactions +* @ref escaping +* @ref performance +* @ref transactor +* @ref datatypes diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/parameters.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/parameters.md new file mode 100644 index 000000000..7ac792025 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/parameters.md @@ -0,0 +1,90 @@ +Statement parameters {#parameters} +==================== + +When you execute a prepared statement (see @ref prepared), or a parameterised +statement (using functions like `pqxx::connection::exec_params`), you may write +special _placeholders_ in the query text. They look like `$1`, `$2`, and so +on. + +If you execute the query and pass parameter values, the call will respectively +substitute the first where it finds `$1`, the second where it finds `$2`, et +cetera. + +Doing this saves you work. If you don't use statement parameters, you'll need +to quote and escape your values (see `connection::quote()` and friends) as you +insert them into your query as literal values. + +Or if you forget to do that, you leave yourself open to horrible +[SQL injection attacks](https://xkcd.com/327/). Trust me, I was born in a town +whose name started with an apostrophe! + +Statement parameters save you this work. With these parameters you can pass +your values as-is, and they will go across the wire to the database in a safe +format. + +In some cases it may even be faster! When a parameter represents binary data +(as in the SQL `BYTEA` type), libpqxx will send it directly as binary, which is +a bit more efficient. If you insert the binary data directly in your query +text, your CPU will have some extra work to do, converting the data into a text +format, escaping it, and adding quotes. + + +Dynamic parameter lists +----------------------- + +In rare cases you may just not know how many parameters you'll pass into your +statement when you call it. + +For these situations, have a look at `params`. It lets you compose your +parameters list on the fly, even add whole ranges of parameters at a time. + +You can pass a `params` into your statement as a normal parameter. It will +fill in all the parameter values it contains into that position of the +statement's overall parameter list. + +So if you call your statement passing a regular parameter `a`, a +`params` containing just a parameter `b`, and another regular parameter `c`, +then your call will pass parameters `a`, `b`, and `c`. Or if the params object +is empty, it will pass just `a` and `c`. If the params object contains `x` and +`y`, your call will pass `a, x, y, c`. + +You can mix static and dynamic parameters freely. Don't go overboard though: +complexity is where bugs happen! + + +Generating placeholders +----------------------- + +If your code gets particularly complex, it may sometimes happen that it becomes +hard to track which parameter value belongs with which placeholder. Did you +intend to pass this numeric value as `$7`, or as `$8`? The answer may depend +on an `if` that happened earlier in a different function. + +(Generally if things get that complex, it's a good idea to look for simpler +solutions. But especially when performance matters, sometimes you can't avoid +complexity like that.) + +There's a little helper class called `placeholders`. You can use it as a +counter which produces those placeholder strings, `$1`, `$2`, `$3`, et cetera. +When you start generating a complex statement, you can create both a `params` +and a `placeholders`: + + pqxx::params values; + pqxx::placeholders name; + +Let's say you've got some complex code to generate the conditions for an SQL +"WHERE" clause. You'll generally want to do these things close together in +your, so that you don't accidentally update one part and forget another: + + if (extra_clause) + { + // Extend the query text, using the current placeholder. + query += " AND x = " + name.get(); + // Add the parameter value. + values.append(my_x); + // Move on to the next placeholder value. + name.next(); + } + +Depending on the starting value of `name`, this might add to `query` a fragment +like "` AND x = $3`" or "` AND x = $5`". diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/performance.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/performance.md new file mode 100644 index 000000000..6c403684f --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/performance.md @@ -0,0 +1,24 @@ +Performance features {#performance} +==================== + +If your program's database interaction is not as efficient as it needs to be, +the first place to look is usually the SQL you're executing. But libpqxx +has a few specialized features to help you squeeze more performance out +of how you issue commands and retrieve data: + +* @ref streams. Use these as a faster way to transfer data between your + code and the database. +* `std::string_view` and `pqxx::zview`. In places where traditional C++ worked + with `std::string`, see whether `std::string_view` or `pqxx::zview` will + do. Of course that means that you'll have to look at the data's lifetime + more carefully, but it'll save the computer a lot of copying. +* @ref prepared. These can be executed many times without the server + parsing and planning them anew each time. They also save you having to + escape string parameters. +* `pqxx::pipeline` lets you send queries to the database in batches, and + continue other processing while they are executing. +* `pqxx::connecting` lets you start setting up a database connection, but + without blocking the thread. + +As always of course, don't risk the quality of your code for optimizations +that you don't need! diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/prepared-statement.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/prepared-statement.md new file mode 100644 index 000000000..5193866a6 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/prepared-statement.md @@ -0,0 +1,125 @@ +Prepared statements {#prepared} +=================== + +Prepared statements are SQL queries that you define once and then invoke +as many times as you like, typically with varying parameters. It's basically +a function that you can define ad hoc. + +If you have an SQL statement that you're going to execute many times in +quick succession, it may be more efficient to prepare it once and reuse it. +This saves the database backend the effort of parsing complex SQL and +figuring out an efficient execution plan. Another nice side effect is that +you don't need to worry about escaping parameters. Some corporate coding +standards require all SQL parameters to be passed in this way, to reduce the +risk of programmer mistakes leaving room for SQL injections. + + +Preparing a statement +--------------------- + +You create a prepared statement by preparing it on the connection (using the +`pqxx::connection::prepare` functions), passing an identifier and its SQL text. + +The identifier is the name by which the prepared statement will be known; it +should consist of ASCII letters, digits, and underscores only, and start with +an ASCII letter. The name is case-sensitive. + +```cxx + void prepare_my_statement(pqxx::connection &c) + { + c.prepare( + "my_statement", + "SELECT * FROM Employee WHERE name = 'Xavier'"); + } +``` + +Once you've done this, you'll be able to call `my_statement` from any +transaction you execute on the same connection. For this, use the +`pqxx::transaction_base::exec_prepared` functions. + +```cxx + pqxx::result execute_my_statement(pqxx::transaction_base &t) + { + return t.exec_prepared("my_statement"); + } +``` + + +Parameters +---------- + +Did I mention that prepared statements can have parameters? The query text +can contain `$1`, `$2` etc. as placeholders for parameter values that you +will provide when you invoke the prepared satement. + +See @ref parameters for more about this. And here's a simple example of +preparing a statement and invoking it with parameters: + +```cxx + void prepare_find(pqxx::connection &c) + { + // Prepare a statement called "find" that looks for employees with a + // given name (parameter 1) whose salary exceeds a given number + // (parameter 2). + c.prepare( + "find", + "SELECT * FROM Employee WHERE name = $1 AND salary > $2"); + } +``` + +This example looks up the prepared statement "find," passes `name` and +`min_salary` as parameters, and invokes the statement with those values: + +```cxx + pqxx::result execute_find( + pqxx::transaction_base &t, std::string name, int min_salary) + { + return t.exec_prepared("find", name, min_salary); + } +``` + + +A special prepared statement +---------------------------- + +There is one special case: the _nameless_ prepared statement. You may prepare +a statement without a name, i.e. whose name is an empty string. The unnamed +statement can be redefined at any time, without un-preparing it first. + + +Performance note +---------------- + +Don't assume that using prepared statements will speed up your application. +There are cases where prepared statements are actually slower than plain SQL. + +The reason is that the backend can often produce a better execution plan when +it knows the statement's actual parameter values. + +For example, say you've got a web application and you're querying for users +with status "inactive" who have email addresses in a given domain name X. If +X is a very popular provider, the best way for the database engine to plan the +query may be to list the inactive users first and then filter for the email +addresses you're looking for. But in other cases, it may be much faster to +find matching email addresses first and then see which of their owners are +"inactive." A prepared statement must be planned to fit either case, but a +direct query will be optimised based on table statistics, partial indexes, etc. + + +Zero bytes +---------- + +@warning Beware of "nul" bytes! + +Any string you pass as a parameter will end at the _first char with value +zero._ If you pass a string that contains a zero byte, the last byte in the +value will be the one just before the zero. + +So, if you need a zero byte in a string, consider that it's really a _binary +string,_ which is not the same thing as a text string. SQL represents binary +data as the `BYTEA` type, or in binary large objects ("blobs"). + +In libpqxx, you represent binary data as a range of `std::byte`. They must be +contiguous in memory, so that libpqxx can pass pointers to the underlying C +library. So you might use `std::basic_string`, or +`std::basic_string_view`, or `std::vector`. diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/streams.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/streams.md new file mode 100644 index 000000000..3df4d6126 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/streams.md @@ -0,0 +1,107 @@ +Streams {#streams} +======= + +Most of the time it's fine to retrieve data from the database using `SELECT` +queries, and store data using `INSERT`. But for those cases where efficiency +matters, there are two classes to help you do this better: `stream_from` and +`stream_to`. They're less flexible than SQL queries, and there's the risk of +losing your connection while you're in mid-stream, but you get some speed and +memory efficiencies in return. + +Both stream classes do data conversion for you: `stream_from` receives values +from the database in PostgreSQL's text format, and converts them to the C++ +types you specify. Likewise, `stream_to` converts C++ values you provide to +PostgreSQL's text format for transfer. (On its end, the database of course +converts values to and from their SQL types.) + + +Null values +----------- + +So how do you deal with nulls? It depends on the C++ type you're using. Some +types may have a built-in null value. For instance, if you have a +`char const *` value and you convert it to an SQL string, then converting a +`nullptr` will produce a NULL SQL value. + +But what do you do about C++ types which don't have a built-in null value, such +as `int`? The trick is to wrap it in `std::optional`. The difference between +`int` and `std::optional` is that the former always has an `int` value, +and the latter doesn't have to. + +Actually it's not just `std::optional`. You can do the same thing with +`std::unique_ptr` or `std::shared_ptr`. A smart pointer is less efficient than +`std::optional` in most situations because they allocate their value on the +heap, but sometimes that's what you want in order to save moving or copying +large values around. + +This part is not generic though. It won't work with just any smart-pointer +type, just the ones which are explicitly supported: `shared_ptr` and +`unique_ptr`. If you really need to, you can build support for additional +wrappers and smart pointers by copying the implementation patterns from the +existing smart-pointer support. + + +stream\_from +------------ + +Use `stream_from` to read data directly from the database. It's faster than +the transaction's `exec` functions if the result contains enough rows. But +also, you won't need to keep your full result set in memory. That can really +matter with larger data sets. + +And, you can start processing your data right after the first row of data comes +in from the server. With `exec()` you need to wait to receive all data, and +then you begin processing. With `stream_from` you can be processing data on +the client side while the server is still sending you the rest. + +You don't actually need to create a `stream_from` object yourself, though you +can. Two shorthand functions, @ref pqxx::transaction_base::stream +and @ref pqxx::transaction_base::for_each, can create the streams for you with +a minimum of overhead. + +Not all kinds of queries will work in a stream. Internally the streams make +use of PostgreSQL's `COPY` command, so see the PostgreSQL documentation for +`COPY` for the exact limitations. Basic `SELECT` and `UPDATE ... RETURNING` +queries should just work. + +As you read a row, the stream converts its fields to a tuple type containing +the value types you ask for: + + auto stream pqxx::stream_from::query( + tx, "SELECT name, points FROM score"); + std::tuple row; + while (stream >> row) + process(row); + stream.complete(); + +As the stream reads each row, it converts that row's data into your tuple, +goes through your loop body, and then promptly forgets that row's data. This +means you can easily process more data than will fit in memory. + + +stream\_to +---------- + +Use `stream_to` to write data directly to a database table. This saves you +having to perform an `INSERT` for every row, and so it can be significantly +faster if you want to insert more than just one or two rows at a time. + +As with `stream_from`, you can specify the table and the columns, and not much +else. You insert tuple-like objects of your choice: + + pqxx::stream_to stream{ + tx, + "score", + std::vector{"name", "points"}}; + for (auto const &entry: scores) + stream << entry; + stream.complete(); + +Each row is processed as you provide it, and not retained in memory after that. + +The call to `complete()` is more important here than it is for `stream_from`. +It's a lot like a "commit" or "abort" at the end of a transaction. If you omit +it, it will be done automatically during the stream's destructor. But since +destructors can't throw exceptions, any failures at that stage won't be visible +in your code. So, always call `complete()` on a `stream_to` to close it off +properly! diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/thread-safety.md b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/thread-safety.md new file mode 100644 index 000000000..07c7f9984 --- /dev/null +++ b/ext/libpqxx-7.7.3/install/ubuntu22.04/arm64/share/doc/libpqxx/thread-safety.md @@ -0,0 +1,29 @@ +Thread safety {#thread-safety} +============= + +This library does not contain any locking code to protect objects against +simultaneous modification in multi-threaded programs. Therefore it is up +to you, the user of the library, to ensure that your threaded client +programs perform no conflicting operations concurrently. + +Most of the time this isn't hard. Result sets are immutable, so you can +share them between threads without problem. The main rule is: + +@li Treat a connection, together with any and all objects related to it, as +a "world" of its own. You should generally make sure that the same "world" +is never accessed by another thread while you're doing anything non-const +in there. + +That means: don't issue a query on a transaction while you're also opening +a subtransaction, don't access a cursor while you may also be committing, +and so on. + +In particular, cursors are tricky. It's easy to perform a non-const +operation without noticing. So, if you're going to share cursors or +cursor-related objects between threads, lock very conservatively! + +Use `pqxx::describe_thread_safety` to find out at runtime what level of +thread safety is implemented in your build and version of libpqxx. It +returns a `pqxx::thread_safety_model` describing what you can and cannot rely +on. A command-line utility `tools/pqxxthreadsafety` prints out the same +information. diff --git a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/pkgconfig/libpqxx.pc b/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/pkgconfig/libpqxx.pc deleted file mode 100644 index 6933e0859..000000000 --- a/ext/libpqxx-7.7.3/install/ubuntu22.04/lib/pkgconfig/libpqxx.pc +++ /dev/null @@ -1,10 +0,0 @@ -prefix=/home/grant/dev/ZeroTierOne/ext/libpqxx-7.7.3/install/ubuntu22.04 -exec_prefix=${prefix} -libdir=${prefix}/lib -includedir=${prefix}/include - -Name: libpqxx -Description: C++ client API for the PostgreSQL database management system. -Version: 7.7.3 -Libs: -L${prefix}/lib -lpqxx -Cflags: -I${prefix}/include diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h index 35231888f..5d8053739 100644 --- a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h @@ -38,7 +38,8 @@ namespace prometheus { static const Metric::Type static_type = Metric::Type::Counter; Counter() : Metric (Metric::Type::Counter) {} ///< \brief Create a counter that starts at 0. - + Counter(const Counter &rhs) : Metric(static_type), value{rhs.value.load()} {} + // original API void Increment() { ///< \brief Increment the counter by 1. diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h index f42308526..fcda14631 100644 --- a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h @@ -38,6 +38,7 @@ namespace prometheus { Gauge() : Metric (static_type) {} ///< \brief Create a gauge that starts at 0. Gauge(const Value value_) : Metric(static_type), value{ value_ } {} ///< \brief Create a gauge that starts at the given amount. + Gauge(const Gauge &rhs) : Metric(rhs.static_type), value{rhs.value.load()} {} // original API diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h index 03cf461ea..0afe41758 100644 --- a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h @@ -27,16 +27,10 @@ namespace prometheus { /// The class is thread-safe. No concurrent call to any API of this type causes /// a data race. template - class Histogram : Metric { - - using BucketBoundaries = std::vector; - - const BucketBoundaries bucket_boundaries_; - std::vector> bucket_counts_; - Gauge sum_; - + class Histogram : public Metric { public: using Value = Value_; + using BucketBoundaries = std::vector; using Family = CustomFamily>; static const Metric::Type static_type = Metric::Type::Histogram; @@ -69,7 +63,7 @@ namespace prometheus { bucket_boundaries_.begin(), std::find_if( std::begin(bucket_boundaries_), std::end(bucket_boundaries_), - [value](const double boundary) { return boundary >= value; }))); + [value](const Value boundary) { return boundary >= value; }))); sum_.Increment(value); bucket_counts_[bucket_index].Increment(); } @@ -105,12 +99,12 @@ namespace prometheus { auto cumulative_count = 0ULL; metric.histogram.bucket.reserve(bucket_counts_.size()); for (std::size_t i{0}; i < bucket_counts_.size(); ++i) { - cumulative_count += static_cast(bucket_counts_[i].Value()); + cumulative_count += static_cast(bucket_counts_[i].Get()); auto bucket = ClientMetric::Bucket{}; bucket.cumulative_count = cumulative_count; bucket.upper_bound = (i == bucket_boundaries_.size() ? std::numeric_limits::infinity() - : bucket_boundaries_[i]); + : static_cast(bucket_boundaries_[i])); metric.histogram.bucket.push_back(std::move(bucket)); } metric.histogram.sample_count = cumulative_count; @@ -119,6 +113,12 @@ namespace prometheus { return metric; } + private: + const BucketBoundaries bucket_boundaries_; + std::vector> bucket_counts_; + Gauge sum_; + + }; /// \brief Return a builder to configure and register a Histogram metric. diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/cmd_formatter.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/cmd_formatter.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/cmd_formatter.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/cmd_formatter.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/command.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/command.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/command.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/command.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/command_args.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/command_args.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/command_args.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/command_args.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/command_options.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/command_options.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/command_options.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/command_options.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/connection.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/connection.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/connection.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/connection.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/connection_pool.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/connection_pool.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/connection_pool.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/connection_pool.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/cxx_utils.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/cxx_utils.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/cxx_utils.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/cxx_utils.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/errors.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/errors.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/errors.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/errors.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/pipeline.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/pipeline.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/pipeline.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/pipeline.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/queued_redis.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/queued_redis.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/queued_redis.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/queued_redis.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/queued_redis.hpp b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/queued_redis.hpp similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/queued_redis.hpp rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/queued_redis.hpp diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis++.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis++.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis++.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis++.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis.hpp b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis.hpp similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis.hpp rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis.hpp diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis_cluster.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis_cluster.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis_cluster.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis_cluster.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis_cluster.hpp b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis_cluster.hpp similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/redis_cluster.hpp rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/redis_cluster.hpp diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/reply.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/reply.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/reply.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/reply.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/sentinel.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/sentinel.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/sentinel.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/sentinel.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/shards.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/shards.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/shards.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/shards.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/shards_pool.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/shards_pool.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/shards_pool.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/shards_pool.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/subscriber.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/subscriber.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/subscriber.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/subscriber.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/tls.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/tls.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/tls.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/tls.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/transaction.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/transaction.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/transaction.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/transaction.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/utils.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/utils.h similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/redis++/utils.h rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/include/sw/redis++/utils.h diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/lib/libredis++.a b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/lib/libredis++.a similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/lib/libredis++.a rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/lib/libredis++.a diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/lib/pkgconfig/redis++.pc b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/lib/pkgconfig/redis++.pc similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/lib/pkgconfig/redis++.pc rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/lib/pkgconfig/redis++.pc diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/share/cmake/redis++/redis++-config-version.cmake b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/share/cmake/redis++/redis++-config-version.cmake similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/share/cmake/redis++/redis++-config-version.cmake rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/share/cmake/redis++/redis++-config-version.cmake diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/share/cmake/redis++/redis++-config.cmake b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/share/cmake/redis++/redis++-config.cmake similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/share/cmake/redis++/redis++-config.cmake rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/share/cmake/redis++/redis++-config.cmake diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/share/cmake/redis++/redis++-targets-release.cmake b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/share/cmake/redis++/redis++-targets-release.cmake similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/share/cmake/redis++/redis++-targets-release.cmake rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/share/cmake/redis++/redis++-targets-release.cmake diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/share/cmake/redis++/redis++-targets.cmake b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/share/cmake/redis++/redis++-targets.cmake similarity index 100% rename from ext/redis-plus-plus-1.3.3/install/ubuntu22.04/share/cmake/redis++/redis++-targets.cmake rename to ext/redis-plus-plus-1.3.3/install/ubuntu22.04/amd64/share/cmake/redis++/redis++-targets.cmake diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/cmd_formatter.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/cmd_formatter.h new file mode 100644 index 000000000..85efdf347 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/cmd_formatter.h @@ -0,0 +1,775 @@ +/************************************************************************** + Copyright (c) 2021 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H +#define SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H + +#include +#include "command_options.h" +#include "command_args.h" +#include "command.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class FormattedCommand { +public: + FormattedCommand(char *data, int len) : _data(data), _size(len) { + if (data == nullptr || len < 0) { + throw Error("failed to format command"); + } + } + + FormattedCommand(const FormattedCommand &) = delete; + FormattedCommand& operator=(const FormattedCommand &) = delete; + + FormattedCommand(FormattedCommand &&that) noexcept { + _move(std::move(that)); + } + + FormattedCommand& operator=(FormattedCommand &&that) noexcept { + if (this != &that) { + _move(std::move(that)); + } + + return *this; + } + + ~FormattedCommand() noexcept { + if (_data != nullptr) { + redisFreeCommand(_data); + } + } + + const char* data() const noexcept { + return _data; + } + + int size() const noexcept { + return _size; + } + +private: + void _move(FormattedCommand &&that) noexcept { + _data = that._data; + _size = that._size; + that._data = nullptr; + that._size = 0; + } + + char *_data = nullptr; + int _size = 0; +}; + +namespace fmt { + +template +FormattedCommand format_cmd(const char *format, Args &&...args) { + char *data = nullptr; + auto len = redisFormatCommand(&data, format, std::forward(args)...); + + return FormattedCommand(data, len); +} + +inline FormattedCommand format_cmd(int argc, const char **argv, const std::size_t *argv_len) { + char *data = nullptr; + auto len = redisFormatCommandArgv(&data, argc, argv, argv_len); + + return FormattedCommand(data, len); +} + +inline FormattedCommand format_cmd(CmdArgs &args) { + char *data = nullptr; + auto len = redisFormatCommandArgv(&data, args.size(), args.argv(), args.argv_len()); + + return FormattedCommand(data, len); +} + +struct SetResultParser { + bool operator()(redisReply &reply) const { + sw::redis::reply::rewrite_set_reply(reply); + return sw::redis::reply::parse(reply); + } +}; + +// CONNECTION commands. + +inline FormattedCommand echo(const StringView &msg) { + return format_cmd("ECHO %b", msg.data(), msg.size()); +} + +inline FormattedCommand ping() { + return format_cmd("PING"); +} + +inline FormattedCommand ping(const StringView &msg) { + return format_cmd("PING %b", msg.data(), msg.size()); +} + +inline FormattedCommand del(const StringView &key) { + return format_cmd("DEL %b", key.data(), key.size()); +} + +template +FormattedCommand del_range(Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "DEL" << std::make_pair(first, last); + + return format_cmd(args); +} + +inline FormattedCommand exists(const StringView &key) { + return format_cmd("EXISTS %b", key.data(), key.size()); +} + +template +FormattedCommand exists_range(Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "EXISTS" << std::make_pair(first, last); + + return format_cmd(args); +} + +inline FormattedCommand expire(const StringView &key, const std::chrono::seconds &timeout) { + return format_cmd("EXPIRE %b %lld", key.data(), key.size(), timeout.count()); +} + +inline FormattedCommand expireat(const StringView &key, + const std::chrono::time_point &tp) { + return format_cmd("EXPIREAT %b %lld", key.data(), key.size(), tp.time_since_epoch().count()); +} + +inline FormattedCommand pexpire(const StringView &key, + const std::chrono::milliseconds &timeout) { + return format_cmd("PEXPIRE %b %lld", key.data(), key.size(), timeout.count()); +} + +inline FormattedCommand pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return format_cmd("PEXPIREAT %b %lld", key.data(), key.size(), tp.time_since_epoch().count()); +} + +inline FormattedCommand pttl(const StringView &key) { + return format_cmd("PTTL %b", key.data(), key.size()); +} + +inline FormattedCommand rename(const StringView &key, const StringView &newkey) { + return format_cmd("RENAME %b %b", key.data(), key.size(), newkey.data(), newkey.size()); +} + +inline FormattedCommand renamenx(const StringView &key, const StringView &newkey) { + return format_cmd("RENAMENX %b %b", key.data(), key.size(), newkey.data(), newkey.size()); +} + +inline FormattedCommand ttl(const StringView &key) { + return format_cmd("TTL %b", key.data(), key.size()); +} + +inline FormattedCommand unlink(const StringView &key) { + return format_cmd("UNLINK %b", key.data(), key.size()); +} + +template +FormattedCommand unlink_range(Input first, Input last) { + CmdArgs args; + args << "UNLINK" << std::make_pair(first, last); + + return format_cmd(args); +} + +// STRING commands. + +inline FormattedCommand get(const StringView &key) { + return format_cmd("GET %b", key.data(), key.size()); +} + +inline FormattedCommand incr(const StringView &key) { + return format_cmd("INCR %b", key.data(), key.size()); +} + +inline FormattedCommand incrby(const StringView &key, long long increment) { + return format_cmd("INCRBY %b %lld", key.data(), key.size(), increment); +} + +inline FormattedCommand incrbyfloat(const StringView &key, double increment) { + return format_cmd("INCRBYFLOAT %b %f", key.data(), key.size(), increment); +} + +template +FormattedCommand mget(Input first, Input last) { + CmdArgs args; + args << "MGET" << std::make_pair(first, last); + + return format_cmd(args); +} + +template +FormattedCommand mset(Input first, Input last) { + CmdArgs args; + args << "MSET" << std::make_pair(first, last); + + return format_cmd(args); +} + +template +FormattedCommand msetnx(Input first, Input last) { + CmdArgs args; + args << "MSETNX" << std::make_pair(first, last); + + return format_cmd(args); +} + +inline FormattedCommand set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + UpdateType type) { + CmdArgs args; + args << "SET" << key << val; + + if (ttl > std::chrono::milliseconds(0)) { + args << "PX" << ttl.count(); + } + + cmd::detail::set_update_type(args, type); + + return format_cmd(args); +} + +inline FormattedCommand strlen(const StringView &key) { + return format_cmd("STRLEN %b", key.data(), key.size()); +} + +inline FormattedCommand blpop(const StringView &key, const std::chrono::seconds &timeout) { + return format_cmd("BLPOP %b %lld", key.data(), key.size(), timeout.count()); +} + +template +FormattedCommand blpop_range(Input first, Input last, const std::chrono::seconds &timeout) { + assert(first != last); + + CmdArgs args; + args << "BLPOP" << std::make_pair(first, last) << timeout.count(); + + return format_cmd(args); +} + +inline FormattedCommand brpop(const StringView &key, const std::chrono::seconds &timeout) { + return format_cmd("BRPOP %b %lld", key.data(), key.size(), timeout.count()); +} + + +template +FormattedCommand brpop_range(Input first, Input last, const std::chrono::seconds &timeout) { + assert(first != last); + + CmdArgs args; + args << "BRPOP" << std::make_pair(first, last) << timeout.count(); + + return format_cmd(args); +} + +inline FormattedCommand brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return format_cmd("BRPOPLPUSH %b %b %lld", + source.data(), source.size(), + destination.data(), destination.size(), + timeout.count()); +} + +inline FormattedCommand llen(const StringView &key) { + return format_cmd("LLEN %b", key.data(), key.size()); +} + +inline FormattedCommand lpop(const StringView &key) { + return format_cmd("LPOP %b", key.data(), key.size()); +} + +inline FormattedCommand lpush(const StringView &key, const StringView &val) { + return format_cmd("LPUSH %b %b", key.data(), key.size(), val.data(), val.size()); +} + +template +FormattedCommand lpush_range(const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "LPUSH" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +inline FormattedCommand lrange(const StringView &key, long long start, long long stop) { + return format_cmd("LRANGE %b %lld %lld", key.data(), key.size(), start, stop); +} + +inline FormattedCommand lrem(const StringView &key, long long count, const StringView &val) { + return format_cmd("LREM %b %lld %b", key.data(), key.size(), count, val.data(), val.size()); +} + +inline FormattedCommand ltrim(const StringView &key, long long start, long long stop) { + return format_cmd("LTRIM %b %lld %lld", key.data(), key.size(), start, stop); +} + +inline FormattedCommand rpop(const StringView &key) { + return format_cmd("RPOP %b", key.data(), key.size()); +} + +inline FormattedCommand rpoplpush(const StringView &source, const StringView &destination) { + return format_cmd("RPOPLPUSH %b %b", + source.data(), source.size(), + destination.data(), destination.size()); +} + +inline FormattedCommand rpush(const StringView &key, const StringView &val) { + return format_cmd("RPUSH %b %b", key.data(), key.size(), val.data(), val.size()); +} + +template +FormattedCommand rpush_range(const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "RPUSH" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +// HASH commands. + +inline FormattedCommand hdel(const StringView &key, const StringView &field) { + return format_cmd("HDEL %b %b", key.data(), key.size(), field.data(), field.size()); +} + +template +FormattedCommand hdel_range(const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "HDEL" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +inline FormattedCommand hexists(const StringView &key, const StringView &field) { + return format_cmd("HEXISTS %b %b", key.data(), key.size(), field.data(), field.size()); +} + +inline FormattedCommand hget(const StringView &key, const StringView &field) { + return format_cmd("HGET %b %b", key.data(), key.size(), field.data(), field.size()); +} + +inline FormattedCommand hgetall(const StringView &key) { + return format_cmd("HGETALL %b", key.data(), key.size()); +} + +inline FormattedCommand hincrby(const StringView &key, + const StringView &field, + long long increment) { + return format_cmd("HINCRBY %b %b %lld", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline FormattedCommand hincrbyfloat(const StringView &key, + const StringView &field, + double increment) { + return format_cmd("HINCRBYFLOAT %b %b %f", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline FormattedCommand hkeys(const StringView &key) { + return format_cmd("HKEYS %b", key.data(), key.size()); +} + +inline FormattedCommand hlen(const StringView &key) { + return format_cmd("HLEN %b", key.data(), key.size()); +} + +template +FormattedCommand hmget(const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "HMGET" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +template +FormattedCommand hmset(const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "HMSET" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +inline FormattedCommand hset(const StringView &key, + const StringView &field, + const StringView &val) { + return format_cmd("HSET %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +template +auto hset_range(const StringView &key, + Input first, + Input last) + -> typename std::enable_if::value, + FormattedCommand>::type { + assert(first != last); + + CmdArgs args; + args << "HSET" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +inline FormattedCommand hvals(const StringView &key) { + return format_cmd("HVALS %b", key.data(), key.size()); +} + +// SET commands. + +inline FormattedCommand sadd(const StringView &key, const StringView &member) { + return format_cmd("SADD %b %b", key.data(), key.size(), member.data(), member.size()); +} + +template +FormattedCommand sadd_range(const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SADD" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +inline FormattedCommand scard(const StringView &key) { + return format_cmd("SCARD %b", key.data(), key.size()); +} + +inline FormattedCommand sismember(const StringView &key, const StringView &member) { + return format_cmd("SISMEMBER %b %b", key.data(), key.size(), member.data(), member.size()); +} + +inline FormattedCommand smembers(const StringView &key) { + return format_cmd("SMEMBERS %b", key.data(), key.size()); +} + +inline FormattedCommand spop(const StringView &key) { + return format_cmd("SPOP %b", key.data(), key.size()); +} + +inline FormattedCommand spop(const StringView &key, long long count) { + return format_cmd("SPOP %b %lld", key.data(), key.size(), count); +} + +inline FormattedCommand srem(const StringView &key, const StringView &member) { + return format_cmd("SREM %b %b", key.data(), key.size(), member.data(), member.size()); +} + +template +FormattedCommand srem_range(const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SREM" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +// SORTED SET commands. + +inline FormattedCommand bzpopmax(const StringView &key, const std::chrono::seconds &timeout) { + return format_cmd("BZPOPMAX %b %lld", key.data(), key.size(), timeout.count()); +} + +template +FormattedCommand bzpopmax_range(Input first, + Input last, + const std::chrono::seconds &timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMAX" << std::make_pair(first, last) << timeout.count(); + + return format_cmd(args); +} + +inline FormattedCommand bzpopmin(const StringView &key, const std::chrono::seconds &timeout) { + return format_cmd("BZPOPMIN %b %lld", key.data(), key.size(), timeout.count()); +} + +template +FormattedCommand bzpopmin_range(Input first, Input last, const std::chrono::seconds &timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMIN" << std::make_pair(first, last) << timeout.count(); + + return format_cmd(args); +} + +inline FormattedCommand zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + CmdArgs args; + args << "ZADD" << key; + + cmd::detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + args << score << member; + + return format_cmd(args); +} + +template +FormattedCommand zadd_range(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + CmdArgs args; + args << "ZADD" << key; + + cmd::detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + while (first != last) { + // Swap the pair to pair. + args << first->second << first->first; + ++first; + } + + return format_cmd(args); +} + +inline FormattedCommand zcard(const StringView &key) { + return format_cmd("ZCARD %b", key.data(), key.size()); +} + +template +FormattedCommand zcount(const StringView &key, const Interval &interval) { + return format_cmd("ZCOUNT %b %s %s", + key.data(), key.size(), + interval.min().c_str(), + interval.max().c_str()); +} + +inline FormattedCommand zincrby(const StringView &key, + double increment, + const StringView &member) { + return format_cmd("ZINCRBY %b %f %b", + key.data(), key.size(), + increment, + member.data(), member.size()); +} + +template +FormattedCommand zlexcount(const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + return format_cmd("ZLEXCOUNT %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline FormattedCommand zpopmax(const StringView &key) { + return format_cmd("ZPOPMAX %b", key.data(), key.size()); +} + +inline FormattedCommand zpopmax(const StringView &key, long long count) { + return format_cmd("ZPOPMAX %b %lld", key.data(), key.size(), count); +} + +inline FormattedCommand zpopmin(const StringView &key) { + return format_cmd("ZPOPMIN %b", key.data(), key.size()); +} + +inline FormattedCommand zpopmin_count(const StringView &key, long long count) { + return format_cmd("ZPOPMIN %b %lld", key.data(), key.size(), count); +} + +inline FormattedCommand zrange(const StringView &key, long long start, long long stop) { + return format_cmd("ZRANGE %b %lld %lld", key.data(), key.size(), start, stop); +} + +template +FormattedCommand zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + return format_cmd("ZRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +template +FormattedCommand zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + return format_cmd("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +inline FormattedCommand zrank(const StringView &key, const StringView &member) { + return format_cmd("ZRANK %b %b", key.data(), key.size(), member.data(), member.size()); +} + +inline FormattedCommand zrem(const StringView &key, const StringView &member) { + return format_cmd("ZREM %b %b", key.data(), key.size(), member.data(), member.size()); +} + +template +FormattedCommand zrem_range(const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "ZREM" << key << std::make_pair(first, last); + + return format_cmd(args); +} + +template +FormattedCommand zremrangebylex(const StringView &key, const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + return format_cmd("ZREMRANGEBYLEX %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline FormattedCommand zremrangebyrank(const StringView &key, long long start, long long stop) { + return format_cmd("ZREMRANGEBYRANK %b %lld %lld", key.data(), key.size(), start, stop); +} + +template +FormattedCommand zremrangebyscore(const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + return format_cmd("ZREMRANGEBYSCORE %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +template +FormattedCommand zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + return format_cmd("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); +} + +inline FormattedCommand zrevrank(const StringView &key, const StringView &member) { + return format_cmd("ZREVRANK %b %b", key.data(), key.size(), member.data(), member.size()); +} + +inline FormattedCommand zscore(const StringView &key, const StringView &member) { + return format_cmd("ZSCORE %b %b", key.data(), key.size(), member.data(), member.size()); +} + +// SCRIPTING commands. +template +FormattedCommand eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + CmdArgs args; + auto keys_num = std::distance(keys_first, keys_last); + + args << "EVAL" << script << keys_num + << std::make_pair(keys_first, keys_last) + << std::make_pair(args_first, args_last); + + return format_cmd(args); +} + +template +FormattedCommand evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + CmdArgs args; + auto keys_num = std::distance(keys_first, keys_last); + + args << "EVALSHA" << script << keys_num + << std::make_pair(keys_first, keys_last) + << std::make_pair(args_first, args_last); + + return format_cmd(args); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CMD_FORMATTER_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command.h new file mode 100644 index 000000000..81857dd9b --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command.h @@ -0,0 +1,2271 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_H + +#include +#include +#include +#include +#include "connection.h" +#include "command_options.h" +#include "command_args.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace cmd { + +// CONNECTION command. +inline void auth(Connection &connection, const StringView &password) { + connection.send("AUTH %b", password.data(), password.size()); +} + +inline void auth(Connection &connection, const StringView &user, const StringView &password) { + connection.send("AUTH %b %b", + user.data(), user.size(), + password.data(), password.size()); +} + +inline void echo(Connection &connection, const StringView &msg) { + connection.send("ECHO %b", msg.data(), msg.size()); +} + +inline void ping(Connection &connection) { + connection.send("PING"); +} + +inline void quit(Connection &connection) { + connection.send("QUIT"); +} + +inline void ping(Connection &connection, const StringView &msg) { + // If *msg* is empty, Redis returns am empty reply of REDIS_REPLY_STRING type. + connection.send("PING %b", msg.data(), msg.size()); +} + +inline void select(Connection &connection, long long idx) { + connection.send("SELECT %lld", idx); +} + +inline void swapdb(Connection &connection, long long idx1, long long idx2) { + connection.send("SWAPDB %lld %lld", idx1, idx2); +} + +// SERVER commands. + +inline void bgrewriteaof(Connection &connection) { + connection.send("BGREWRITEAOF"); +} + +inline void bgsave(Connection &connection) { + connection.send("BGSAVE"); +} + +inline void dbsize(Connection &connection) { + connection.send("DBSIZE"); +} + +inline void flushall(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHALL ASYNC"); + } else { + connection.send("FLUSHALL"); + } +} + +inline void flushdb(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHDB ASYNC"); + } else { + connection.send("FLUSHDB"); + } +} + +inline void info(Connection &connection) { + connection.send("INFO"); +} + +inline void info(Connection &connection, const StringView §ion) { + connection.send("INFO %b", section.data(), section.size()); +} + +inline void lastsave(Connection &connection) { + connection.send("LASTSAVE"); +} + +inline void save(Connection &connection) { + connection.send("SAVE"); +} + +// KEY commands. + +inline void del(Connection &connection, const StringView &key) { + connection.send("DEL %b", key.data(), key.size()); +} + +template +inline void del_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "DEL" << std::make_pair(first, last); + + connection.send(args); +} + +inline void dump(Connection &connection, const StringView &key) { + connection.send("DUMP %b", key.data(), key.size()); +} + +inline void exists(Connection &connection, const StringView &key) { + connection.send("EXISTS %b", key.data(), key.size()); +} + +template +inline void exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void expire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("EXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void expireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("EXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void keys(Connection &connection, const StringView &pattern) { + connection.send("KEYS %b", pattern.data(), pattern.size()); +} + +inline void move(Connection &connection, const StringView &key, long long db) { + connection.send("MOVE %b %lld", + key.data(), key.size(), + db); +} + +inline void persist(Connection &connection, const StringView &key) { + connection.send("PERSIST %b", key.data(), key.size()); +} + +inline void pexpire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("PEXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void pexpireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("PEXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void pttl(Connection &connection, const StringView &key) { + connection.send("PTTL %b", key.data(), key.size()); +} + +inline void randomkey(Connection &connection) { + connection.send("RANDOMKEY"); +} + +inline void rename(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAME %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +inline void renamenx(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAMENX %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace); + +inline void scan(Connection &connection, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SCAN %lld MATCH %b COUNT %lld", + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void touch(Connection &connection, const StringView &key) { + connection.send("TOUCH %b", key.data(), key.size()); +} + +template +inline void touch_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "TOUCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void ttl(Connection &connection, const StringView &key) { + connection.send("TTL %b", key.data(), key.size()); +} + +inline void type(Connection &connection, const StringView &key) { + connection.send("TYPE %b", key.data(), key.size()); +} + +inline void unlink(Connection &connection, const StringView &key) { + connection.send("UNLINK %b", key.data(), key.size()); +} + +template +inline void unlink_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "UNLINK" << std::make_pair(first, last); + + connection.send(args); +} + +inline void wait(Connection &connection, long long numslave, long long timeout) { + connection.send("WAIT %lld %lld", numslave, timeout); +} + +// STRING commands. + +inline void append(Connection &connection, const StringView &key, const StringView &str) { + connection.send("APPEND %b %b", + key.data(), key.size(), + str.data(), str.size()); +} + +inline void bitcount(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("BITCOUNT %b %lld %lld", + key.data(), key.size(), + start, end); +} + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key); + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last); + +inline void bitpos(Connection &connection, + const StringView &key, + long long bit, + long long start, + long long end) { + connection.send("BITPOS %b %lld %lld %lld", + key.data(), key.size(), + bit, + start, + end); +} + +inline void decr(Connection &connection, const StringView &key) { + connection.send("DECR %b", key.data(), key.size()); +} + +inline void decrby(Connection &connection, const StringView &key, long long decrement) { + connection.send("DECRBY %b %lld", + key.data(), key.size(), + decrement); +} + +inline void get(Connection &connection, const StringView &key) { + connection.send("GET %b", + key.data(), key.size()); +} + +inline void getbit(Connection &connection, const StringView &key, long long offset) { + connection.send("GETBIT %b %lld", + key.data(), key.size(), + offset); +} + +inline void getrange(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("GETRANGE %b %lld %lld", + key.data(), key.size(), + start, + end); +} + +inline void getset(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("GETSET %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void incr(Connection &connection, const StringView &key) { + connection.send("INCR %b", key.data(), key.size()); +} + +inline void incrby(Connection &connection, const StringView &key, long long increment) { + connection.send("INCRBY %b %lld", + key.data(), key.size(), + increment); +} + +inline void incrbyfloat(Connection &connection, const StringView &key, double increment) { + connection.send("INCRBYFLOAT %b %f", + key.data(), key.size(), + increment); +} + +template +inline void mget(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MGET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void mset(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void msetnx(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSETNX" << std::make_pair(first, last); + + connection.send(args); +} + +inline void psetex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("PSETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type); + +inline void setex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("SETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +inline void setnx(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("SETNX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void setrange(Connection &connection, + const StringView &key, + long long offset, + const StringView &val) { + connection.send("SETRANGE %b %lld %b", + key.data(), key.size(), + offset, + val.data(), val.size()); +} + +inline void strlen(Connection &connection, const StringView &key) { + connection.send("STRLEN %b", key.data(), key.size()); +} + +// LIST commands. + +inline void blpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BLPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void blpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BLPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BRPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void brpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BRPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpoplpush(Connection &connection, + const StringView &source, + const StringView &destination, + long long timeout) { + connection.send("BRPOPLPUSH %b %b %lld", + source.data(), source.size(), + destination.data(), destination.size(), + timeout); +} + +inline void lindex(Connection &connection, const StringView &key, long long index) { + connection.send("LINDEX %b %lld", + key.data(), key.size(), + index); +} + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + +inline void llen(Connection &connection, + const StringView &key) { + connection.send("LLEN %b", key.data(), key.size()); +} + +inline void lpop(Connection &connection, const StringView &key) { + connection.send("LPOP %b", + key.data(), key.size()); +} + +inline void lpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void lpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "LPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void lpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void lrange(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void lrem(Connection &connection, + const StringView &key, + long long count, + const StringView &val) { + connection.send("LREM %b %lld %b", + key.data(), key.size(), + count, + val.data(), val.size()); +} + +inline void lset(Connection &connection, + const StringView &key, + long long index, + const StringView &val) { + connection.send("LSET %b %lld %b", + key.data(), key.size(), + index, + val.data(), val.size()); +} + +inline void ltrim(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LTRIM %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void rpop(Connection &connection, const StringView &key) { + connection.send("RPOP %b", key.data(), key.size()); +} + +inline void rpoplpush(Connection &connection, + const StringView &source, + const StringView &destination) { + connection.send("RPOPLPUSH %b %b", + source.data(), source.size(), + destination.data(), destination.size()); +} + +inline void rpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void rpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "RPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void rpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +// HASH commands. + +inline void hdel(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HDEL %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +template +inline void hdel_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hexists(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HEXISTS %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hget(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HGET %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hgetall(Connection &connection, const StringView &key) { + connection.send("HGETALL %b", key.data(), key.size()); +} + +inline void hincrby(Connection &connection, + const StringView &key, + const StringView &field, + long long increment) { + connection.send("HINCRBY %b %b %lld", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hincrbyfloat(Connection &connection, + const StringView &key, + const StringView &field, + double increment) { + connection.send("HINCRBYFLOAT %b %b %f", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hkeys(Connection &connection, const StringView &key) { + connection.send("HKEYS %b", key.data(), key.size()); +} + +inline void hlen(Connection &connection, const StringView &key) { + connection.send("HLEN %b", key.data(), key.size()); +} + +template +inline void hmget(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMGET" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void hmset(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMSET" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("HSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void hset(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSET %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +template +void hset_range(Connection &connection, const StringView &key, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "HSET" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hsetnx(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSETNX %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hstrlen(Connection &connection, + const StringView &key, + const StringView &field) { + connection.send("HSTRLEN %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hvals(Connection &connection, const StringView &key) { + connection.send("HVALS %b", key.data(), key.size()); +} + +// SET commands + +inline void sadd(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SADD %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void sadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void scard(Connection &connection, const StringView &key) { + connection.send("SCARD %b", key.data(), key.size()); +} + +template +inline void sdiff(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFF" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sdiffstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SDIFFSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sdiffstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFFSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void sinter(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTER" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sinterstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SINTERSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTERSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +inline void sismember(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SISMEMBER %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void smembers(Connection &connection, const StringView &key) { + connection.send("SMEMBERS %b", key.data(), key.size()); +} + +inline void smove(Connection &connection, + const StringView &source, + const StringView &destination, + const StringView &member) { + connection.send("SMOVE %b %b %b", + source.data(), source.size(), + destination.data(), destination.size(), + member.data(), member.size()); +} + +inline void spop(Connection &connection, const StringView &key) { + connection.send("SPOP %b", key.data(), key.size()); +} + +inline void spop_range(Connection &connection, const StringView &key, long long count) { + connection.send("SPOP %b %lld", + key.data(), key.size(), + count); +} + +inline void srandmember(Connection &connection, const StringView &key) { + connection.send("SRANDMEMBER %b", key.data(), key.size()); +} + +inline void srandmember_range(Connection &connection, + const StringView &key, + long long count) { + connection.send("SRANDMEMBER %b %lld", + key.data(), key.size(), + count); +} + +inline void srem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void srem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void sscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +template +inline void sunion(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNION" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sunionstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SUNIONSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNIONSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// Sorted Set commands. + +inline void bzpopmax(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMAX %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmax_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMAX" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void bzpopmin(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMIN %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmin_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMIN" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed); + +inline void zadd(Connection &connection, + const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto tmp = {std::make_pair(member, score)}; + + zadd_range(connection, key, tmp.begin(), tmp.end(), type, changed); +} + +inline void zcard(Connection &connection, const StringView &key) { + connection.send("ZCARD %b", key.data(), key.size()); +} + +template +inline void zcount(Connection &connection, + const StringView &key, + const Interval &interval) { + connection.send("ZCOUNT %b %s %s", + key.data(), key.size(), + interval.min().c_str(), + interval.max().c_str()); +} + +inline void zincrby(Connection &connection, + const StringView &key, + double increment, + const StringView &member) { + connection.send("ZINCRBY %b %f %b", + key.data(), key.size(), + increment, + member.data(), member.size()); +} + +inline void zinterstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZINTERSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +template +inline void zlexcount(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZLEXCOUNT %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zpopmax(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMAX %b %lld", + key.data(), key.size(), + count); +} + +inline void zpopmin(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMIN %b %lld", + key.data(), key.size(), + count); +} + +inline void zrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +template +void zrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } else { + connection.send("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } +} + +inline void zrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zrem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void zrem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "ZREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void zremrangebylex(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYLEX %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zremrangebyrank(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("zremrangebyrank %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +template +inline void zremrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYSCORE %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zrevrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZREVRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZREVRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrevrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); +} + +template +void zrevrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZREVRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } else { + connection.send("ZREVRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } +} + +inline void zrevrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREVRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("ZSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void zscore(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZSCORE %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zunionstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZUNIONSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +// HYPERLOGLOG commands. + +inline void pfadd(Connection &connection, + const StringView &key, + const StringView &element) { + connection.send("PFADD %b %b", + key.data(), key.size(), + element.data(), element.size()); +} + +template +inline void pfadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfcount(Connection &connection, const StringView &key) { + connection.send("PFCOUNT %b", key.data(), key.size()); +} + +template +inline void pfcount_range(Connection &connection, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFCOUNT" << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfmerge(Connection &connection, const StringView &destination, const StringView &key) { + connection.send("PFMERGE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void pfmerge_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFMERGE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// GEO commands. + +inline void geoadd(Connection &connection, + const StringView &key, + const std::tuple &member) { + const auto &mem = std::get<0>(member); + + connection.send("GEOADD %b %f %f %b", + key.data(), key.size(), + std::get<1>(member), + std::get<2>(member), + mem.data(), mem.size()); +} + +template +inline void geoadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOADD" << key; + + while (first != last) { + const auto &member = *first; + args << std::get<1>(member) << std::get<2>(member) << std::get<0>(member); + ++first; + } + + connection.send(args); +} + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit); + +inline void geohash(Connection &connection, const StringView &key, const StringView &member) { + connection.send("GEOHASH %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void geohash_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOHASH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void geopos(Connection &connection, const StringView &key, const StringView &member) { + connection.send("GEOPOS %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void geopos_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOPOS" << key << std::make_pair(first, last); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +// SCRIPTING commands. + +template +inline void eval(Connection &connection, + const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + CmdArgs cmd_args; + + auto keys_num = std::distance(keys_first, keys_last); + + cmd_args << "EVAL" << script << keys_num + << std::make_pair(keys_first, keys_last) + << std::make_pair(args_first, args_last); + + connection.send(cmd_args); +} + +template +inline void evalsha(Connection &connection, + const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + CmdArgs cmd_args; + + auto keys_num = std::distance(keys_first, keys_last); + + cmd_args << "EVALSHA" << script << keys_num + << std::make_pair(keys_first, keys_last) + << std::make_pair(args_first, args_last); + + connection.send(cmd_args); +} + +inline void script_exists(Connection &connection, const StringView &sha) { + connection.send("SCRIPT EXISTS %b", sha.data(), sha.size()); +} + +template +inline void script_exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SCRIPT" << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void script_flush(Connection &connection) { + connection.send("SCRIPT FLUSH"); +} + +inline void script_kill(Connection &connection) { + connection.send("SCRIPT KILL"); +} + +inline void script_load(Connection &connection, const StringView &script) { + connection.send("SCRIPT LOAD %b", script.data(), script.size()); +} + +// PUBSUB commands. + +inline void psubscribe(Connection &connection, const StringView &pattern) { + connection.send("PSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void psubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void publish(Connection &connection, + const StringView &channel, + const StringView &message) { + connection.send("PUBLISH %b %b", + channel.data(), channel.size(), + message.data(), message.size()); +} + +inline void punsubscribe(Connection &connection) { + connection.send("PUNSUBSCRIBE"); +} + +inline void punsubscribe(Connection &connection, const StringView &pattern) { + connection.send("PUNSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void punsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PUNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PUNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void subscribe(Connection &connection, const StringView &channel) { + connection.send("SUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void subscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("SUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "SUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void unsubscribe(Connection &connection) { + connection.send("UNSUBSCRIBE"); +} + +inline void unsubscribe(Connection &connection, const StringView &channel) { + connection.send("UNSUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void unsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "UNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +// Transaction commands. + +inline void discard(Connection &connection) { + connection.send("DISCARD"); +} + +inline void exec(Connection &connection) { + connection.send("EXEC"); +} + +inline void multi(Connection &connection) { + connection.send("MULTI"); +} + +inline void unwatch(Connection &connection) { + connection.send("UNWATCH"); +} + +template +inline void unwatch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNWATCH: no key specified"); + } + + CmdArgs args; + args << "UNWATCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void watch(Connection &connection, const StringView &key) { + connection.send("WATCH %b", key.data(), key.size()); +} + +template +inline void watch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("WATCH: no key specified"); + } + + CmdArgs args; + args << "WATCH" << std::make_pair(first, last); + + connection.send(args); +} + +// Stream commands. + +inline void xack(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XACK %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +template +void xack_range(Connection &connection, + const StringView &key, + const StringView &group, + Input first, + Input last) { + CmdArgs args; + args << "XACK" << key << group << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last) { + CmdArgs args; + args << "XADD" << key << id << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_maxlen_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + CmdArgs args; + args << "XADD" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count << id << std::make_pair(first, last); + + connection.send(args); +} + +inline void xclaim(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + const StringView &id) { + connection.send("XCLAIM %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size(), + min_idle_time, + id.data(), id.size()); +} + +template +void xclaim_range(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + Input first, + Input last) { + CmdArgs args; + args << "XCLAIM" << key << group << consumer << min_idle_time << std::make_pair(first, last); + + connection.send(args); +} + +inline void xdel(Connection &connection, const StringView &key, const StringView &id) { + connection.send("XDEL %b %b", key.data(), key.size(), id.data(), id.size()); +} + +template +void xdel_range(Connection &connection, const StringView &key, Input first, Input last) { + CmdArgs args; + args << "XDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void xgroup_create(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + CmdArgs args; + args << "XGROUP" << "CREATE" << key << group << id; + + if (mkstream) { + args << "MKSTREAM"; + } + + connection.send(args); +} + +inline void xgroup_setid(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XGROUP SETID %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +inline void xgroup_destroy(Connection &connection, + const StringView &key, + const StringView &group) { + connection.send("XGROUP DESTROY %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xgroup_delconsumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer) { + connection.send("XGROUP DELCONSUMER %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size()); +} + +inline void xlen(Connection &connection, const StringView &key) { + connection.send("XLEN %b", key.data(), key.size()); +} + +inline void xpending(Connection &connection, const StringView &key, const StringView &group) { + connection.send("XPENDING %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xpending_detail(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XPENDING %b %b %b %b %lld", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xpending_per_consumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + connection.send("XPENDING %b %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count, + consumer.data(), consumer.size()); +} + +inline void xrange(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end) { + connection.send("XRANGE %b %b %b", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size()); +} + +inline void xrange_count(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xread(Connection &connection, + const StringView &key, + const StringView &id, + long long count) { + connection.send("XREAD COUNT %lld STREAMS %b %b", + count, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_range(Connection &connection, Input first, Input last, long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xread_block(Connection &connection, + const StringView &key, + const StringView &id, + long long timeout, + long long count) { + connection.send("XREAD COUNT %lld BLOCK %lld STREAMS %b %b", + count, + timeout, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_block_range(Connection &connection, + Input first, + Input last, + long long timeout, + long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "BLOCK" << timeout << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup_block(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_block_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xrevrange(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start) { + connection.send("XREVRANGE %b %b %b", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size()); +} + +inline void xrevrange_count(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + connection.send("XREVRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size(), + count); +} + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx); + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op); + +void set_update_type(CmdArgs &args, UpdateType type); + +void set_aggregation_type(CmdArgs &args, Aggregation type); + +template +void zinterstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zinterstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit); + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +} + +} + +} + +} + +namespace sw { + +namespace redis { + +namespace cmd { + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + assert(first != last); + + CmdArgs args; + + args << "ZADD" << key; + + detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + while (first != last) { + // Swap the pair to pair. + args << first->second << first->first; + ++first; + } + + connection.send(args); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zinterstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zunionstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command_args.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command_args.h new file mode 100644 index 000000000..0beb71e5c --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command_args.h @@ -0,0 +1,180 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +class CmdArgs { +public: + template + CmdArgs& append(Arg &&arg); + + template + CmdArgs& append(Arg &&arg, Args &&...args); + + // All overloads of operator<< are for internal use only. + CmdArgs& operator<<(const StringView &arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& operator<<(T &&arg); + + template + CmdArgs& operator<<(const std::pair &range); + + template + auto operator<<(const std::tuple &) -> + typename std::enable_if::type { + return *this; + } + + template + auto operator<<(const std::tuple &arg) -> + typename std::enable_if::type; + + const char** argv() { + return _argv.data(); + } + + const std::size_t* argv_len() { + return _argv_len.data(); + } + + std::size_t size() const { + return _argv.size(); + } + +private: + // Deep copy. + CmdArgs& _append(std::string arg); + + // Shallow copy. + CmdArgs& _append(const StringView &arg); + + // Shallow copy. + CmdArgs& _append(const char *arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& _append(T &&arg) { + return operator<<(std::forward(arg)); + } + + template + CmdArgs& _append(std::true_type, const std::pair &range); + + template + CmdArgs& _append(std::false_type, const std::pair &range); + + std::vector _argv; + std::vector _argv_len; + + std::list _args; +}; + +template +inline CmdArgs& CmdArgs::append(Arg &&arg) { + return _append(std::forward(arg)); +} + +template +inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) { + _append(std::forward(arg)); + + return append(std::forward(args)...); +} + +inline CmdArgs& CmdArgs::operator<<(const StringView &arg) { + _argv.push_back(arg.data()); + _argv_len.push_back(arg.size()); + + return *this; +} + +template +inline CmdArgs& CmdArgs::operator<<(const std::pair &range) { + return _append(IsKvPair())>::type>(), range); +} + +template ::type>::value, + int>::type> +inline CmdArgs& CmdArgs::operator<<(T &&arg) { + return _append(std::to_string(std::forward(arg))); +} + +template +auto CmdArgs::operator<<(const std::tuple &arg) -> + typename std::enable_if::type { + operator<<(std::get(arg)); + + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(std::string arg) { + _args.push_back(std::move(arg)); + return operator<<(_args.back()); +} + +inline CmdArgs& CmdArgs::_append(const StringView &arg) { + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(const char *arg) { + return operator<<(arg); +} + +template +CmdArgs& CmdArgs::_append(std::false_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << *first; + ++first; + } + + return *this; +} + +template +CmdArgs& CmdArgs::_append(std::true_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << first->first << first->second; + ++first; + } + + return *this; +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command_options.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command_options.h new file mode 100644 index 000000000..ca766c086 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/command_options.h @@ -0,0 +1,211 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class UpdateType { + EXIST, + NOT_EXIST, + ALWAYS +}; + +enum class InsertPosition { + BEFORE, + AFTER +}; + +enum class BoundType { + CLOSED, + OPEN, + LEFT_OPEN, + RIGHT_OPEN +}; + +// (-inf, +inf) +template +class UnboundedInterval; + +// [min, max], (min, max), (min, max], [min, max) +template +class BoundedInterval; + +// [min, +inf), (min, +inf) +template +class LeftBoundedInterval; + +// (-inf, max], (-inf, max) +template +class RightBoundedInterval; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(double min, double max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(double min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(double max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(const std::string &min, const std::string &max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(const std::string &min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(const std::string &max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +struct LimitOptions { + long long offset = 0; + long long count = -1; +}; + +enum class Aggregation { + SUM, + MIN, + MAX +}; + +enum class BitOp { + AND, + OR, + XOR, + NOT +}; + +enum class GeoUnit { + M, + KM, + MI, + FT +}; + +template +struct WithCoord : TupleWithType, T> {}; + +template +struct WithDist : TupleWithType {}; + +template +struct WithHash : TupleWithType {}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/connection.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/connection.h new file mode 100644 index 000000000..340375561 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/connection.h @@ -0,0 +1,237 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "reply.h" +#include "utils.h" +#include "tls.h" + +namespace sw { + +namespace redis { + +enum class ConnectionType { + TCP = 0, + UNIX +}; + +struct ConnectionOptions { +public: + ConnectionOptions() = default; + + explicit ConnectionOptions(const std::string &uri); + + ConnectionOptions(const ConnectionOptions &) = default; + ConnectionOptions& operator=(const ConnectionOptions &) = default; + + ConnectionOptions(ConnectionOptions &&) = default; + ConnectionOptions& operator=(ConnectionOptions &&) = default; + + ~ConnectionOptions() = default; + + ConnectionType type = ConnectionType::TCP; + + std::string host; + + int port = 6379; + + std::string path; + + std::string user = "default"; + + std::string password; + + int db = 0; + + bool keep_alive = false; + + std::chrono::milliseconds connect_timeout{0}; + + std::chrono::milliseconds socket_timeout{0}; + + tls::TlsOptions tls; + + // `readonly` is only used for reading from a slave node in Redis Cluster mode. + // Client code should never manually set/get it. This member might be removed in the future. + bool readonly = false; + +private: + ConnectionOptions _parse_uri(const std::string &uri) const; + + auto _split_uri(const std::string &uri) const + -> std::tuple; + + auto _split_path(const std::string &path) const + -> std::tuple; + + void _parse_parameters(const std::string ¶meter_string, + ConnectionOptions &opts) const; + + void _set_option(const std::string &key, const std::string &val, ConnectionOptions &opts) const; + + bool _parse_bool_option(const std::string &str) const; + + std::chrono::milliseconds _parse_timeout_option(const std::string &str) const; + + std::vector _split(const std::string &str, const std::string &delimiter) const; + + void _set_auth_opts(const std::string &auth, ConnectionOptions &opts) const; + + void _set_tcp_opts(const std::string &path, ConnectionOptions &opts) const; + + void _set_unix_opts(const std::string &path, ConnectionOptions &opts) const; +}; + +class CmdArgs; + +class Connection { +public: + explicit Connection(const ConnectionOptions &opts); + + Connection(const Connection &) = delete; + Connection& operator=(const Connection &) = delete; + + Connection(Connection &&) = default; + Connection& operator=(Connection &&) = default; + + ~Connection() = default; + + // Check if the connection is broken. Client needs to do this check + // before sending some command to the connection. If it's broken, + // client needs to reconnect it. + bool broken() const noexcept { + return _ctx->err != REDIS_OK; + } + + void reset() noexcept { + _ctx->err = 0; + } + + void invalidate() noexcept { + _ctx->err = REDIS_ERR; + } + + void reconnect(); + + auto create_time() const + -> std::chrono::time_point { + return _create_time; + } + + auto last_active() const + -> std::chrono::time_point { + return _last_active; + } + + template + void send(const char *format, Args &&...args); + + void send(int argc, const char **argv, const std::size_t *argv_len); + + void send(CmdArgs &args); + + ReplyUPtr recv(bool handle_error_reply = true); + + const ConnectionOptions& options() const { + return _opts; + } + + friend void swap(Connection &lhs, Connection &rhs) noexcept; + +private: + class Connector; + + struct ContextDeleter { + void operator()(redisContext *context) const { + if (context != nullptr) { + redisFree(context); + } + }; + }; + + using ContextUPtr = std::unique_ptr; + + void _set_options(); + + void _auth(); + + void _select_db(); + + void _enable_readonly(); + + redisContext* _context(); + + ContextUPtr _ctx; + + // The time that the connection is created. + std::chrono::time_point _create_time{}; + + // The time that the connection is created or the time that + // the connection is recently used, i.e. `_context()` is called. + std::chrono::time_point _last_active{}; + + ConnectionOptions _opts; + + // TODO: define _tls_ctx before _ctx + tls::TlsContextUPtr _tls_ctx; +}; + +using ConnectionSPtr = std::shared_ptr; + +enum class Role { + MASTER, + SLAVE +}; + +// Inline implementaions. + +template +inline void Connection::send(const char *format, Args &&...args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommand(ctx, + format, + std::forward(args)...) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +inline redisContext* Connection::_context() { + _last_active = std::chrono::steady_clock::now(); + + return _ctx.get(); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/connection_pool.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/connection_pool.h new file mode 100644 index 000000000..30ba5daa2 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/connection_pool.h @@ -0,0 +1,182 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H + +#include +#include +#include +#include +#include +#include +#include "connection.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +struct ConnectionPoolOptions { + // Max number of connections, including both in-use and idle ones. + std::size_t size = 1; + + // Max time to wait for a connection. 0ms means client waits forever. + std::chrono::milliseconds wait_timeout{0}; + + // Max lifetime of a connection. 0ms means we never expire the connection. + std::chrono::milliseconds connection_lifetime{0}; + + // Max idle time of a connection. 0ms means we never expire the connection. + std::chrono::milliseconds connection_idle_time{0}; +}; + +class ConnectionPool { +public: + ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool() = default; + + ConnectionPool(ConnectionPool &&that); + ConnectionPool& operator=(ConnectionPool &&that); + + ConnectionPool(const ConnectionPool &) = delete; + ConnectionPool& operator=(const ConnectionPool &) = delete; + + ~ConnectionPool() = default; + + // Fetch a connection from pool. + Connection fetch(); + + ConnectionOptions connection_options(); + + void release(Connection connection); + + // Create a new connection. + Connection create(); + + ConnectionPool clone(); + +private: + void _move(ConnectionPool &&that); + + // NOT thread-safe + Connection _create(); + + Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked); + + Connection _fetch(); + + void _wait_for_connection(std::unique_lock &lock); + + bool _need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime, + const std::chrono::milliseconds &connection_idle_time) const; + + void _update_connection_opts(const std::string &host, int port) { + _opts.host = host; + _opts.port = port; + } + + bool _role_changed(const ConnectionOptions &opts) const { + return opts.port != _opts.port || opts.host != _opts.host; + } + + ConnectionOptions _opts; + + ConnectionPoolOptions _pool_opts; + + std::deque _pool; + + std::size_t _used_connections = 0; + + std::mutex _mutex; + + std::condition_variable _cv; + + SimpleSentinel _sentinel; +}; + +using ConnectionPoolSPtr = std::shared_ptr; + +class SafeConnection { +public: + explicit SafeConnection(ConnectionPool &pool) : _pool(pool), _connection(_pool.fetch()) { + assert(!_connection.broken()); + } + + SafeConnection(const SafeConnection &) = delete; + SafeConnection& operator=(const SafeConnection &) = delete; + + SafeConnection(SafeConnection &&) = delete; + SafeConnection& operator=(SafeConnection &&) = delete; + + ~SafeConnection() { + _pool.release(std::move(_connection)); + } + + Connection& connection() { + return _connection; + } + +private: + ConnectionPool &_pool; + Connection _connection; +}; + +// NOTE: This class is similar to `SafeConnection`. +// The difference is that `SafeConnection` tries to avoid copying a std::shared_ptr. +class GuardedConnection { +public: + explicit GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool), + _connection(_pool->fetch()) { + assert(!_connection.broken()); + } + + GuardedConnection(const GuardedConnection &) = delete; + GuardedConnection& operator=(const GuardedConnection &) = delete; + + GuardedConnection(GuardedConnection &&) = default; + GuardedConnection& operator=(GuardedConnection &&) = default; + + ~GuardedConnection() { + // If `GuardedConnection` has been moved, `_pool` will be nullptr. + if (_pool) { + _pool->release(std::move(_connection)); + } + } + + Connection& connection() { + return _connection; + } + +private: + ConnectionPoolSPtr _pool; + Connection _connection; +}; + +using GuardedConnectionSPtr = std::shared_ptr; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/cxx_utils.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/cxx_utils.h new file mode 100644 index 000000000..dce51abe9 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/cxx_utils.h @@ -0,0 +1,46 @@ +/************************************************************************** + Copyright (c) 2021 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CXX_UTILS_H +#define SEWENEW_REDISPLUSPLUS_CXX_UTILS_H + +#include +#include +#include + +#define REDIS_PLUS_PLUS_HAS_OPTIONAL + +#define REDIS_PLUS_PLUS_HAS_VARIANT + +namespace sw { + +namespace redis { + +using StringView = std::string_view; + +template +using Optional = std::optional; + +template +using Variant = std::variant; + +using Monostate = std::monostate; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CXX_UTILS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/errors.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/errors.h new file mode 100644 index 000000000..7cc19d2f1 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/errors.h @@ -0,0 +1,166 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H +#define SEWENEW_REDISPLUSPLUS_ERRORS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +enum ReplyErrorType { + ERR, + MOVED, + ASK +}; + +class Error : public std::exception { +public: + explicit Error(const std::string &msg) : _msg(msg) {} + + Error(const Error &) = default; + Error& operator=(const Error &) = default; + + Error(Error &&) = default; + Error& operator=(Error &&) = default; + + virtual ~Error() override = default; + + virtual const char* what() const noexcept override { + return _msg.data(); + } + +private: + std::string _msg; +}; + +class IoError : public Error { +public: + explicit IoError(const std::string &msg) : Error(msg) {} + + IoError(const IoError &) = default; + IoError& operator=(const IoError &) = default; + + IoError(IoError &&) = default; + IoError& operator=(IoError &&) = default; + + virtual ~IoError() override = default; +}; + +class TimeoutError : public IoError { +public: + explicit TimeoutError(const std::string &msg) : IoError(msg) {} + + TimeoutError(const TimeoutError &) = default; + TimeoutError& operator=(const TimeoutError &) = default; + + TimeoutError(TimeoutError &&) = default; + TimeoutError& operator=(TimeoutError &&) = default; + + virtual ~TimeoutError() override = default; +}; + +class ClosedError : public Error { +public: + explicit ClosedError(const std::string &msg) : Error(msg) {} + + ClosedError(const ClosedError &) = default; + ClosedError& operator=(const ClosedError &) = default; + + ClosedError(ClosedError &&) = default; + ClosedError& operator=(ClosedError &&) = default; + + virtual ~ClosedError() override = default; +}; + +class ProtoError : public Error { +public: + explicit ProtoError(const std::string &msg) : Error(msg) {} + + ProtoError(const ProtoError &) = default; + ProtoError& operator=(const ProtoError &) = default; + + ProtoError(ProtoError &&) = default; + ProtoError& operator=(ProtoError &&) = default; + + virtual ~ProtoError() override = default; +}; + +class OomError : public Error { +public: + explicit OomError(const std::string &msg) : Error(msg) {} + + OomError(const OomError &) = default; + OomError& operator=(const OomError &) = default; + + OomError(OomError &&) = default; + OomError& operator=(OomError &&) = default; + + virtual ~OomError() override = default; +}; + +class ReplyError : public Error { +public: + explicit ReplyError(const std::string &msg) : Error(msg) {} + + ReplyError(const ReplyError &) = default; + ReplyError& operator=(const ReplyError &) = default; + + ReplyError(ReplyError &&) = default; + ReplyError& operator=(ReplyError &&) = default; + + virtual ~ReplyError() override = default; +}; + +class WatchError : public Error { +public: + explicit WatchError() : Error("Watched key has been modified") {} + + WatchError(const WatchError &) = default; + WatchError& operator=(const WatchError &) = default; + + WatchError(WatchError &&) = default; + WatchError& operator=(WatchError &&) = default; + + virtual ~WatchError() override = default; +}; + + +// MovedError and AskError are defined in shards.h +class MovedError; + +class AskError; + +void throw_error(const redisContext &context, const std::string &err_info); + +void throw_error(const redisReply &reply); + +template +inline void range_check(const char *cmd, Input first, Input last) { + if (first == last) { + throw Error(std::string(cmd) + ": no key specified"); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/pipeline.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/pipeline.h new file mode 100644 index 000000000..52b01253f --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/pipeline.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H +#define SEWENEW_REDISPLUSPLUS_PIPELINE_H + +#include +#include +#include "connection.h" + +namespace sw { + +namespace redis { + +class PipelineImpl { +public: + template + void command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + } + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t /*cmd_num*/) { + // Reconnect to Redis to discard all commands. + connection.reconnect(); + } +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/queued_redis.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/queued_redis.h new file mode 100644 index 000000000..e663c7423 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/queued_redis.h @@ -0,0 +1,2013 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "connection_pool.h" +#include "utils.h" +#include "reply.h" +#include "command.h" +#include "redis.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class QueuedReplies; + +// If any command throws, QueuedRedis resets the connection, and becomes invalid. +// In this case, the only thing we can do is to destory the QueuedRedis object. +template +class QueuedRedis { +public: + QueuedRedis(QueuedRedis &&) = default; + QueuedRedis& operator=(QueuedRedis &&) = default; + + // When it destructs, the underlying *Connection* will be closed or return to pool, + // and any command that has NOT been executed will be ignored. + ~QueuedRedis(); + + Redis redis(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type; + + template + QueuedRedis& command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type; + + QueuedReplies exec(); + + void discard(); + + // CONNECTION commands. + + QueuedRedis& auth(const StringView &password) { + return command(cmd::auth, password); + } + + QueuedRedis& auth(const StringView &user, const StringView &password) { + return command( + cmd::auth, user, password); + } + + QueuedRedis& echo(const StringView &msg) { + return command(cmd::echo, msg); + } + + QueuedRedis& ping() { + return command(cmd::ping); + } + + QueuedRedis& ping(const StringView &msg) { + return command(cmd::ping, msg); + } + + // We DO NOT support the QUIT command. See *Redis::quit* doc for details. + // + // QueuedRedis& quit(); + + QueuedRedis& select(long long idx) { + return command(cmd::select, idx); + } + + QueuedRedis& swapdb(long long idx1, long long idx2) { + return command(cmd::swapdb, idx1, idx2); + } + + // SERVER commands. + + QueuedRedis& bgrewriteaof() { + return command(cmd::bgrewriteaof); + } + + QueuedRedis& bgsave() { + return command(cmd::bgsave); + } + + QueuedRedis& dbsize() { + return command(cmd::dbsize); + } + + QueuedRedis& flushall(bool async = false) { + return command(cmd::flushall, async); + } + + QueuedRedis& flushdb(bool async = false) { + return command(cmd::flushdb, async); + } + + QueuedRedis& info() { + return command(cmd::info); + } + + QueuedRedis& info(const StringView §ion) { + return command(cmd::info, section); + } + + QueuedRedis& lastsave() { + return command(cmd::lastsave); + } + + QueuedRedis& save() { + return command(cmd::save); + } + + // KEY commands. + + QueuedRedis& del(const StringView &key) { + return command(cmd::del, key); + } + + template + QueuedRedis& del(Input first, Input last) { + range_check("DEL", first, last); + + return command(cmd::del_range, first, last); + } + + template + QueuedRedis& del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + QueuedRedis& dump(const StringView &key) { + return command(cmd::dump, key); + } + + QueuedRedis& exists(const StringView &key) { + return command(cmd::exists, key); + } + + template + QueuedRedis& exists(Input first, Input last) { + range_check("EXISTS", first, last); + + return command(cmd::exists_range, first, last); + } + + template + QueuedRedis& exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + QueuedRedis& expire(const StringView &key, long long timeout) { + return command(cmd::expire, key, timeout); + } + + QueuedRedis& expire(const StringView &key, + const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); + } + + QueuedRedis& expireat(const StringView &key, long long timestamp) { + return command(cmd::expireat, key, timestamp); + } + + QueuedRedis& expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& keys(const StringView &pattern) { + return command(cmd::keys, pattern); + } + + QueuedRedis& move(const StringView &key, long long db) { + return command(cmd::move, key, db); + } + + QueuedRedis& persist(const StringView &key) { + return command(cmd::persist, key); + } + + QueuedRedis& pexpire(const StringView &key, long long timeout) { + return command(cmd::pexpire, key, timeout); + } + + QueuedRedis& pexpire(const StringView &key, + const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); + } + + QueuedRedis& pexpireat(const StringView &key, long long timestamp) { + return command(cmd::pexpireat, key, timestamp); + } + + QueuedRedis& pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& pttl(const StringView &key) { + return command(cmd::pttl, key); + } + + QueuedRedis& randomkey() { + return command(cmd::randomkey); + } + + QueuedRedis& rename(const StringView &key, const StringView &newkey) { + return command(cmd::rename, key, newkey); + } + + QueuedRedis& renamenx(const StringView &key, const StringView &newkey) { + return command(cmd::renamenx, key, newkey); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false) { + return command(cmd::restore, key, val, ttl, replace); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false) { + return restore(key, val, ttl.count(), replace); + } + + // TODO: sort + + QueuedRedis& scan(long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::scan, cursor, pattern, count); + } + + QueuedRedis& scan(long long cursor) { + return scan(cursor, "*", 10); + } + + QueuedRedis& scan(long long cursor, + const StringView &pattern) { + return scan(cursor, pattern, 10); + } + + QueuedRedis& scan(long long cursor, + long long count) { + return scan(cursor, "*", count); + } + + QueuedRedis& touch(const StringView &key) { + return command(cmd::touch, key); + } + + template + QueuedRedis& touch(Input first, Input last) { + range_check("TOUCH", first, last); + + return command(cmd::touch_range, first, last); + } + + template + QueuedRedis& touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + QueuedRedis& ttl(const StringView &key) { + return command(cmd::ttl, key); + } + + QueuedRedis& type(const StringView &key) { + return command(cmd::type, key); + } + + QueuedRedis& unlink(const StringView &key) { + return command(cmd::unlink, key); + } + + template + QueuedRedis& unlink(Input first, Input last) { + range_check("UNLINK", first, last); + + return command(cmd::unlink_range, first, last); + } + + template + QueuedRedis& unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + QueuedRedis& wait(long long numslaves, long long timeout) { + return command(cmd::wait, numslaves, timeout); + } + + QueuedRedis& wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); + } + + // STRING commands. + + QueuedRedis& append(const StringView &key, const StringView &str) { + return command(cmd::append, key, str); + } + + QueuedRedis& bitcount(const StringView &key, + long long start = 0, + long long end = -1) { + return command(cmd::bitcount, key, start, end); + } + + QueuedRedis& bitop(BitOp op, + const StringView &destination, + const StringView &key) { + return command(cmd::bitop, op, destination, key); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + Input first, + Input last) { + range_check("BITOP", first, last); + + return command(cmd::bitop_range, op, destination, first, last); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + QueuedRedis& bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1) { + return command(cmd::bitpos, key, bit, start, end); + } + + QueuedRedis& decr(const StringView &key) { + return command(cmd::decr, key); + } + + QueuedRedis& decrby(const StringView &key, long long decrement) { + return command(cmd::decrby, key, decrement); + } + + QueuedRedis& get(const StringView &key) { + return command(cmd::get, key); + } + + QueuedRedis& getbit(const StringView &key, long long offset) { + return command(cmd::getbit, key, offset); + } + + QueuedRedis& getrange(const StringView &key, long long start, long long end) { + return command(cmd::getrange, key, start, end); + } + + QueuedRedis& getset(const StringView &key, const StringView &val) { + return command(cmd::getset, key, val); + } + + QueuedRedis& incr(const StringView &key) { + return command(cmd::incr, key); + } + + QueuedRedis& incrby(const StringView &key, long long increment) { + return command(cmd::incrby, key, increment); + } + + QueuedRedis& incrbyfloat(const StringView &key, double increment) { + return command(cmd::incrbyfloat, key, increment); + } + + template + QueuedRedis& mget(Input first, Input last) { + range_check("MGET", first, last); + + return command(cmd::mget, first, last); + } + + template + QueuedRedis& mget(std::initializer_list il) { + return mget(il.begin(), il.end()); + } + + template + QueuedRedis& mset(Input first, Input last) { + range_check("MSET", first, last); + + return command(cmd::mset, first, last); + } + + template + QueuedRedis& mset(std::initializer_list il) { + return mset(il.begin(), il.end()); + } + + template + QueuedRedis& msetnx(Input first, Input last) { + range_check("MSETNX", first, last); + + return command(cmd::msetnx, first, last); + } + + template + QueuedRedis& msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + QueuedRedis& psetex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::psetex, key, ttl, val); + } + + QueuedRedis& psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); + } + + QueuedRedis& set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS) { + _set_cmd_indexes.push_back(_cmd_num); + + return command(cmd::set, key, val, ttl.count(), type); + } + + QueuedRedis& setex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::setex, key, ttl, val); + } + + QueuedRedis& setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + return setex(key, ttl.count(), val); + } + + QueuedRedis& setnx(const StringView &key, const StringView &val) { + return command(cmd::setnx, key, val); + } + + QueuedRedis& setrange(const StringView &key, + long long offset, + const StringView &val) { + return command(cmd::setrange, key, offset, val); + } + + QueuedRedis& strlen(const StringView &key) { + return command(cmd::strlen, key); + } + + // LIST commands. + + QueuedRedis& blpop(const StringView &key, long long timeout) { + return command(cmd::blpop, key, timeout); + } + + QueuedRedis& blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(key, timeout.count()); + } + + template + QueuedRedis& blpop(Input first, Input last, long long timeout) { + range_check("BLPOP", first, last); + + return command(cmd::blpop_range, first, last, timeout); + } + + template + QueuedRedis& blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(first, last, timeout.count()); + } + + template + QueuedRedis& blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpop(const StringView &key, long long timeout) { + return command(cmd::brpop, key, timeout); + } + + QueuedRedis& brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(key, timeout.count()); + } + + template + QueuedRedis& brpop(Input first, Input last, long long timeout) { + range_check("BRPOP", first, last); + + return command(cmd::brpop_range, first, last, timeout); + } + + template + QueuedRedis& brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(first, last, timeout.count()); + } + + template + QueuedRedis& brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + return command(cmd::brpoplpush, source, destination, timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpoplpush(source, destination, timeout.count()); + } + + QueuedRedis& lindex(const StringView &key, long long index) { + return command(cmd::lindex, key, index); + } + + QueuedRedis& linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + return command(cmd::linsert, key, position, pivot, val); + } + + QueuedRedis& llen(const StringView &key) { + return command(cmd::llen, key); + } + + QueuedRedis& lpop(const StringView &key) { + return command(cmd::lpop, key); + } + + QueuedRedis& lpush(const StringView &key, const StringView &val) { + return command(cmd::lpush, key, val); + } + + template + QueuedRedis& lpush(const StringView &key, Input first, Input last) { + range_check("LPUSH", first, last); + + return command(cmd::lpush_range, key, first, last); + } + + template + QueuedRedis& lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + QueuedRedis& lpushx(const StringView &key, const StringView &val) { + return command(cmd::lpushx, key, val); + } + + QueuedRedis& lrange(const StringView &key, + long long start, + long long stop) { + return command(cmd::lrange, key, start, stop); + } + + QueuedRedis& lrem(const StringView &key, long long count, const StringView &val) { + return command(cmd::lrem, key, count, val); + } + + QueuedRedis& lset(const StringView &key, long long index, const StringView &val) { + return command(cmd::lset, key, index, val); + } + + QueuedRedis& ltrim(const StringView &key, long long start, long long stop) { + return command(cmd::ltrim, key, start, stop); + } + + QueuedRedis& rpop(const StringView &key) { + return command(cmd::rpop, key); + } + + QueuedRedis& rpoplpush(const StringView &source, const StringView &destination) { + return command(cmd::rpoplpush, source, destination); + } + + QueuedRedis& rpush(const StringView &key, const StringView &val) { + return command(cmd::rpush, key, val); + } + + template + QueuedRedis& rpush(const StringView &key, Input first, Input last) { + range_check("RPUSH", first, last); + + return command(cmd::rpush_range, key, first, last); + } + + template + QueuedRedis& rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + QueuedRedis& rpushx(const StringView &key, const StringView &val) { + return command(cmd::rpushx, key, val); + } + + // HASH commands. + + QueuedRedis& hdel(const StringView &key, const StringView &field) { + return command(cmd::hdel, key, field); + } + + template + QueuedRedis& hdel(const StringView &key, Input first, Input last) { + range_check("HDEL", first, last); + + return command(cmd::hdel_range, key, first, last); + } + + template + QueuedRedis& hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + QueuedRedis& hexists(const StringView &key, const StringView &field) { + return command(cmd::hexists, key, field); + } + + QueuedRedis& hget(const StringView &key, const StringView &field) { + return command(cmd::hget, key, field); + } + + QueuedRedis& hgetall(const StringView &key) { + return command(cmd::hgetall, key); + } + + QueuedRedis& hincrby(const StringView &key, + const StringView &field, + long long increment) { + return command(cmd::hincrby, key, field, increment); + } + + QueuedRedis& hincrbyfloat(const StringView &key, + const StringView &field, + double increment) { + return command(cmd::hincrbyfloat, key, field, increment); + } + + QueuedRedis& hkeys(const StringView &key) { + return command(cmd::hkeys, key); + } + + QueuedRedis& hlen(const StringView &key) { + return command(cmd::hlen, key); + } + + template + QueuedRedis& hmget(const StringView &key, Input first, Input last) { + range_check("HMGET", first, last); + + return command(cmd::hmget, key, first, last); + } + + template + QueuedRedis& hmget(const StringView &key, std::initializer_list il) { + return hmget(key, il.begin(), il.end()); + } + + template + QueuedRedis& hmset(const StringView &key, Input first, Input last) { + range_check("HMSET", first, last); + + return command(cmd::hmset, key, first, last); + } + + template + QueuedRedis& hmset(const StringView &key, std::initializer_list il) { + return hmset(key, il.begin(), il.end()); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::hscan, key, cursor, pattern, count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return hscan(key, cursor, pattern, 10); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + long long count) { + return hscan(key, cursor, "*", count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor) { + return hscan(key, cursor, "*", 10); + } + + QueuedRedis& hset(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hset, key, field, val); + } + + QueuedRedis& hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); + } + + template + auto hset(const StringView &key, Input first, Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + range_check("HSET", first, last); + + return command(cmd::hset_range, key, first, last); + } + + template + QueuedRedis& hset(const StringView &key, std::initializer_list il) { + return hset(key, il.begin(), il.end()); + } + + QueuedRedis& hsetnx(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hsetnx, key, field, val); + } + + QueuedRedis& hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); + } + + QueuedRedis& hstrlen(const StringView &key, const StringView &field) { + return command(cmd::hstrlen, key, field); + } + + QueuedRedis& hvals(const StringView &key) { + return command(cmd::hvals, key); + } + + // SET commands. + + QueuedRedis& sadd(const StringView &key, const StringView &member) { + return command(cmd::sadd, key, member); + } + + template + QueuedRedis& sadd(const StringView &key, Input first, Input last) { + range_check("SADD", first, last); + + return command(cmd::sadd_range, key, first, last); + } + + template + QueuedRedis& sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + QueuedRedis& scard(const StringView &key) { + return command(cmd::scard, key); + } + + template + QueuedRedis& sdiff(Input first, Input last) { + range_check("SDIFF", first, last); + + return command(cmd::sdiff, first, last); + } + + template + QueuedRedis& sdiff(std::initializer_list il) { + return sdiff(il.begin(), il.end()); + } + + QueuedRedis& sdiffstore(const StringView &destination, const StringView &key) { + return command(cmd::sdiffstore, destination, key); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, + Input first, + Input last) { + range_check("SDIFFSTORE", first, last); + + return command(cmd::sdiffstore_range, destination, first, last); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + QueuedRedis& sinter(Input first, Input last) { + range_check("SINTER", first, last); + + return command(cmd::sinter, first, last); + } + + template + QueuedRedis& sinter(std::initializer_list il) { + return sinter(il.begin(), il.end()); + } + + QueuedRedis& sinterstore(const StringView &destination, const StringView &key) { + return command(cmd::sinterstore, destination, key); + } + + template + QueuedRedis& sinterstore(const StringView &destination, + Input first, + Input last) { + range_check("SINTERSTORE", first, last); + + return command(cmd::sinterstore_range, destination, first, last); + } + + template + QueuedRedis& sinterstore(const StringView &destination, std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + QueuedRedis& sismember(const StringView &key, const StringView &member) { + return command(cmd::sismember, key, member); + } + + QueuedRedis& smembers(const StringView &key) { + return command(cmd::smembers, key); + } + + QueuedRedis& smove(const StringView &source, + const StringView &destination, + const StringView &member) { + return command(cmd::smove, source, destination, member); + } + + QueuedRedis& spop(const StringView &key) { + return command(cmd::spop, key); + } + + QueuedRedis& spop(const StringView &key, long long count) { + return command(cmd::spop_range, key, count); + } + + QueuedRedis& srandmember(const StringView &key) { + return command(cmd::srandmember, key); + } + + QueuedRedis& srandmember(const StringView &key, long long count) { + return command(cmd::srandmember_range, key, count); + } + + QueuedRedis& srem(const StringView &key, const StringView &member) { + return command(cmd::srem, key, member); + } + + template + QueuedRedis& srem(const StringView &key, Input first, Input last) { + range_check("SREM", first, last); + + return command(cmd::srem_range, key, first, last); + } + + template + QueuedRedis& srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::sscan, key, cursor, pattern, count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return sscan(key, cursor, pattern, 10); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + long long count) { + return sscan(key, cursor, "*", count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor) { + return sscan(key, cursor, "*", 10); + } + + template + QueuedRedis& sunion(Input first, Input last) { + range_check("SUNION", first, last); + + return command(cmd::sunion, first, last); + } + + template + QueuedRedis& sunion(std::initializer_list il) { + return sunion(il.begin(), il.end()); + } + + QueuedRedis& sunionstore(const StringView &destination, const StringView &key) { + return command(cmd::sunionstore, destination, key); + } + + template + QueuedRedis& sunionstore(const StringView &destination, Input first, Input last) { + range_check("SUNIONSTORE", first, last); + + return command(cmd::sunionstore_range, destination, first, last); + } + + template + QueuedRedis& sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + QueuedRedis& bzpopmax(const StringView &key, long long timeout) { + return command(cmd::bzpopmax, key, timeout); + } + + QueuedRedis& bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(key, timeout.count()); + } + + template + QueuedRedis& bzpopmax(Input first, Input last, long long timeout) { + range_check("BZPOPMAX", first, last); + + return command(cmd::bzpopmax_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, long long timeout) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, long long timeout) { + return command(cmd::bzpopmin, key, timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(key, timeout.count()); + } + + template + QueuedRedis& bzpopmin(Input first, Input last, long long timeout) { + range_check("BZPOPMIN", first, last); + + return command(cmd::bzpopmin_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, long long timeout) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + QueuedRedis& zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd, key, member, score, type, changed); + } + + template + QueuedRedis& zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + range_check("ZADD", first, last); + + return command(cmd::zadd_range, key, first, last, type, changed); + } + + QueuedRedis& zcard(const StringView &key) { + return command(cmd::zcard, key); + } + + template + QueuedRedis& zcount(const StringView &key, const Interval &interval) { + return command(cmd::zcount, key, interval); + } + + QueuedRedis& zincrby(const StringView &key, double increment, const StringView &member) { + return command(cmd::zincrby, key, increment, member); + } + + QueuedRedis& zinterstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zinterstore, destination, key, weight); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + range_check("ZINTERSTORE", first, last); + + return command(cmd::zinterstore_range, destination, first, last, type); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + QueuedRedis& zlexcount(const StringView &key, const Interval &interval) { + return command(cmd::zlexcount, key, interval); + } + + QueuedRedis& zpopmax(const StringView &key) { + _empty_array_cmd_indexes.push_back(_cmd_num); + + return command(cmd::zpopmax, key, 1); + } + + QueuedRedis& zpopmax(const StringView &key, long long count) { + return command(cmd::zpopmax, key, count); + } + + QueuedRedis& zpopmin(const StringView &key) { + _empty_array_cmd_indexes.push_back(_cmd_num); + + return command(cmd::zpopmin, key, 1); + } + + QueuedRedis& zpopmin(const StringView &key, long long count) { + return command(cmd::zpopmin, key, count); + } + + // NOTE: *QueuedRedis::zrange*'s parameters are different from *Redis::zrange*. + // *Redis::zrange* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::zrange*. So we have to use an extra parameter: *with_scores*, + // to decide whether we should send *WITHSCORES* option to Redis. This also applies to + // other commands with the *WITHSCORES* option, e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, + // *ZREVRANGEBYSCORE*. + QueuedRedis& zrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrangebylex(const StringView &key, const Interval &interval) { + return zrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrank(const StringView &key, const StringView &member) { + return command(cmd::zrank, key, member); + } + + QueuedRedis& zrem(const StringView &key, const StringView &member) { + return command(cmd::zrem, key, member); + } + + template + QueuedRedis& zrem(const StringView &key, Input first, Input last) { + range_check("ZREM", first, last); + + return command(cmd::zrem_range, key, first, last); + } + + template + QueuedRedis& zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + QueuedRedis& zremrangebylex(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebylex, key, interval); + } + + QueuedRedis& zremrangebyrank(const StringView &key, long long start, long long stop) { + return command(cmd::zremrangebyrank, key, start, stop); + } + + template + QueuedRedis& zremrangebyscore(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebyscore, key, interval); + } + + // See comments on *ZRANGE*. + QueuedRedis& zrevrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrevrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrevrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, const Interval &interval) { + return zrevrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrevrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrevrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrevrank(const StringView &key, const StringView &member) { + return command(cmd::zrevrank, key, member); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::zscan, key, cursor, pattern, count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return zscan(key, cursor, pattern, 10); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + long long count) { + return zscan(key, cursor, "*", count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor) { + return zscan(key, cursor, "*", 10); + } + + QueuedRedis& zscore(const StringView &key, const StringView &member) { + return command(cmd::zscore, key, member); + } + + QueuedRedis& zunionstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zunionstore, destination, key, weight); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + range_check("ZUNIONSTORE", first, last); + + return command(cmd::zunionstore_range, destination, first, last, type); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + QueuedRedis& pfadd(const StringView &key, const StringView &element) { + return command(cmd::pfadd, key, element); + } + + template + QueuedRedis& pfadd(const StringView &key, Input first, Input last) { + range_check("PFADD", first, last); + + return command(cmd::pfadd_range, key, first, last); + } + + template + QueuedRedis& pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + QueuedRedis& pfcount(const StringView &key) { + return command(cmd::pfcount, key); + } + + template + QueuedRedis& pfcount(Input first, Input last) { + range_check("PFCOUNT", first, last); + + return command(cmd::pfcount_range, first, last); + } + + template + QueuedRedis& pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + QueuedRedis& pfmerge(const StringView &destination, const StringView &key) { + return command(cmd::pfmerge, destination, key); + } + + template + QueuedRedis& pfmerge(const StringView &destination, Input first, Input last) { + range_check("PFMERGE", first, last); + + return command(cmd::pfmerge_range, destination, first, last); + } + + template + QueuedRedis& pfmerge(const StringView &destination, std::initializer_list il) { + return pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + QueuedRedis& geoadd(const StringView &key, + const std::tuple &member) { + return command(cmd::geoadd, key, member); + } + + template + QueuedRedis& geoadd(const StringView &key, + Input first, + Input last) { + range_check("GEOADD", first, last); + + return command(cmd::geoadd_range, key, first, last); + } + + template + QueuedRedis& geoadd(const StringView &key, std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + QueuedRedis& geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M) { + return command(cmd::geodist, key, member1, member2, unit); + } + + // Call reply::parse_leniently to parse the reply. + QueuedRedis& geohash(const StringView &key, const StringView &member) { + return command(cmd::geohash, key, member); + } + + template + QueuedRedis& geohash(const StringView &key, Input first, Input last) { + range_check("GEOHASH", first, last); + + return command(cmd::geohash_range, key, first, last); + } + + template + QueuedRedis& geohash(const StringView &key, std::initializer_list il) { + return geohash(key, il.begin(), il.end()); + } + + // Call reply::parse_leniently to parse the reply. + QueuedRedis& geopos(const StringView &key, const StringView &member) { + return command(cmd::geopos, key, member); + } + + template + QueuedRedis& geopos(const StringView &key, Input first, Input last) { + range_check("GEOPOS", first, last); + + return command(cmd::geopos_range, key, first, last); + } + + template + QueuedRedis& geopos(const StringView &key, std::initializer_list il) { + return geopos(key, il.begin(), il.end()); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _empty_array_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + } + + // NOTE: *QueuedRedis::georadius*'s parameters are different from *Redis::georadius*. + // *Redis::georadius* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::georadius*. So we have to use extra parameters to decide + // whether we should send options to Redis. This also applies to *GEORADIUSBYMEMBER*. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _empty_array_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + destination, + store_dist, + count); + } + + // See the comments on *GEORADIUS*. + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + // SCRIPTING commands. + + template + QueuedRedis& eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + return command(cmd::eval, script, keys_first, keys_last, args_first, args_last); + } + + QueuedRedis& eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return eval(script, keys.begin(), keys.end(), args.begin(), args.end()); + } + + template + QueuedRedis& evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + return command(cmd::evalsha, script, keys_first, keys_last, args_first, args_last); + } + + QueuedRedis& evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return evalsha(script, keys.begin(), keys.end(), args.begin(), args.end()); + } + + // Call reply::parse_leniently to parse the reply. + QueuedRedis& script_exists(const StringView &sha1) { + return command(cmd::script_exists, sha1); + } + + template + QueuedRedis& script_exists(Input first, Input last) { + range_check("SCRIPT EXISTS", first, last); + + return command(cmd::script_exists_range, first, last); + } + + template + QueuedRedis& script_exists(std::initializer_list il) { + return script_exists(il.begin(), il.end()); + } + + QueuedRedis& script_flush() { + return command(cmd::script_flush); + } + + QueuedRedis& script_kill() { + return command(cmd::script_kill); + } + + QueuedRedis& script_load(const StringView &script) { + return command(cmd::script_load, script); + } + + // PUBSUB commands. + + QueuedRedis& publish(const StringView &channel, const StringView &message) { + return command(cmd::publish, channel, message); + } + + // Stream commands. + + QueuedRedis& xack(const StringView &key, const StringView &group, const StringView &id) { + return command(cmd::xack, key, group, id); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, Input first, Input last) { + range_check("XACK", first, last); + + return command(cmd::xack_range, key, group, first, last); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, Input first, Input last) { + range_check("XADD", first, last); + + return command(cmd::xadd_range, key, id, first, last); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true) { + range_check("XADD", first, last); + + return command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id) { + return command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last) { + range_check("XCLAIM", first, last); + + return command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il) { + return xclaim(key, group, consumer, min_idle_time, il.begin(), il.end()); + } + + QueuedRedis& xdel(const StringView &key, const StringView &id) { + return command(cmd::xdel, key, id); + } + + template + QueuedRedis& xdel(const StringView &key, Input first, Input last) { + range_check("XDEL", first, last); + + return command(cmd::xdel_range, key, first, last); + } + + template + QueuedRedis& xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + QueuedRedis& xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false) { + return command(cmd::xgroup_create, key, group, id, mkstream); + } + + QueuedRedis& xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + return command(cmd::xgroup_setid, key, group, id); + } + + QueuedRedis& xgroup_destroy(const StringView &key, const StringView &group) { + return command(cmd::xgroup_destroy, key, group); + } + + QueuedRedis& xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + return command(cmd::xgroup_delconsumer, key, group, consumer); + } + + QueuedRedis& xlen(const StringView &key) { + return command(cmd::xlen, key); + } + + QueuedRedis& xpending(const StringView &key, const StringView &group) { + return command(cmd::xpending, key, group); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xpending_detail, key, group, start, end, count); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + return command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end) { + return command(cmd::xrange, key, start, end); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xrange_count, key, start, end, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id, long long count) { + return command(cmd::xread, key, id, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id) { + return xread(key, id, 0); + } + + template + auto xread(Input first, Input last, long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + range_check("XREAD", first, last); + + return command(cmd::xread_range, first, last, count); + } + + template + auto xread(Input first, Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, 0); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return command(cmd::xread_block, key, id, timeout.count(), count); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xread(key, id, timeout, 0); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + range_check("XREAD", first, last); + + return command(cmd::xread_block_range, first, last, timeout.count(), count); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, timeout, 0); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + return command(cmd::xreadgroup, group, consumer, key, id, count, noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count) { + return xreadgroup(group, consumer, key, id, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + range_check("XREADGROUP", first, last); + + return command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, 0, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) { + return command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return xreadgroup(group, consumer, key, id, timeout, count, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xreadgroup(group, consumer, key, id, timeout, 0, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + range_check("XREADGROUP", first, last); + + return command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, 0, false); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start) { + return command(cmd::xrevrange, key, end, start); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + return command(cmd::xrevrange_count, key, end, start, count); + } + + QueuedRedis& xtrim(const StringView &key, long long count, bool approx = true) { + return command(cmd::xtrim, key, count, approx); + } + +private: + friend class Redis; + + friend class RedisCluster; + + template + QueuedRedis(const ConnectionPoolSPtr &pool, bool new_connection, Args &&...args); + + Connection& _connection(); + + void _sanity_check(); + + void _reset(bool reset_connection = true); + + void _return_connection(); + + void _invalidate(); + + void _clean_up(); + + void _rewrite_replies(std::vector &replies) const; + + template + void _rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const; + + GuardedConnectionSPtr _guarded_connection; + + ConnectionPoolSPtr _connection_pool; + + bool _new_connection = true; + + Impl _impl; + + std::size_t _cmd_num = 0; + + std::vector _set_cmd_indexes; + + std::vector _empty_array_cmd_indexes; + + bool _valid = true; +}; + +class QueuedReplies { +public: + QueuedReplies() = default; + + QueuedReplies(const QueuedReplies &) = delete; + QueuedReplies& operator=(const QueuedReplies &) = delete; + + QueuedReplies(QueuedReplies &&) = default; + QueuedReplies& operator=(QueuedReplies &&) = default; + + ~QueuedReplies() = default; + + std::size_t size() const; + + redisReply& get(std::size_t idx); + + template + Result get(std::size_t idx); + + template + void get(std::size_t idx, Output output); + +private: + template + friend class QueuedRedis; + + explicit QueuedReplies(std::vector replies) : _replies(std::move(replies)) {} + + void _index_check(std::size_t idx) const; + + std::vector _replies; +}; + +} + +} + +#include "queued_redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/queued_redis.hpp b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/queued_redis.hpp new file mode 100644 index 000000000..d2865e46e --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/queued_redis.hpp @@ -0,0 +1,275 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP + +namespace sw { + +namespace redis { + +template +template +QueuedRedis::QueuedRedis(const ConnectionPoolSPtr &pool, + bool new_connection, + Args &&...args) : + _new_connection(new_connection), + _impl(std::forward(args)...) { + assert(pool); + + if (_new_connection) { + _connection_pool = std::make_shared(pool->clone()); + } else { + // Create a connection from the origin pool. + _connection_pool = pool; + } +} + +template +QueuedRedis::~QueuedRedis() { + try { + _clean_up(); + } catch (const Error &e) { + // Ensure the destructor does not throw + } +} + +template +Redis QueuedRedis::redis() { + _sanity_check(); + + assert(_guarded_connection); + + return Redis(_guarded_connection); +} + +template +template +auto QueuedRedis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type { + try { + _sanity_check(); + + _impl.command(_connection(), cmd, std::forward(args)...); + + ++_cmd_num; + } catch (const Error &e) { + _invalidate(); + throw; + } + + return *this; +} + +template +template +QueuedRedis& QueuedRedis::command(const StringView &cmd_name, Args &&...args) { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +template +auto QueuedRedis::command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +QueuedReplies QueuedRedis::exec() { + try { + _sanity_check(); + + auto replies = _impl.exec(_connection(), _cmd_num); + + _rewrite_replies(replies); + + _reset(); + + return QueuedReplies(std::move(replies)); + } catch (const WatchError &e) { + // In this case, we only clear some states and keep the connection, + // so that user can retry the transaction. + _reset(false); + throw; + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::discard() { + try { + _sanity_check(); + + _impl.discard(_connection(), _cmd_num); + + _reset(); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +Connection& QueuedRedis::_connection() { + assert(_valid); + + if (!_guarded_connection) { + _guarded_connection = std::make_shared(_connection_pool); + } + + return _guarded_connection->connection(); +} + +template +void QueuedRedis::_sanity_check() { + if (!_valid) { + throw Error("Not in valid state"); + } + + if (_connection().broken()) { + throw Error("Connection is broken"); + } +} + +template +inline void QueuedRedis::_reset(bool reset_connection) { + if (reset_connection && !_new_connection) { + _return_connection(); + } + + _cmd_num = 0; + + _set_cmd_indexes.clear(); + + _empty_array_cmd_indexes.clear(); +} + +template +inline void QueuedRedis::_return_connection() { + if (_guarded_connection.use_count() == 1) { + // If no one else holding the connection, return it back to pool. + // Instead, if some other `Redis` object holds the connection, + // e.g. `auto redis = transaction.redis();`, we cannot return the connection. + _guarded_connection.reset(); + } +} + +template +void QueuedRedis::_invalidate() { + _valid = false; + + _clean_up(); + + _reset(); +} + +template +void QueuedRedis::_clean_up() { + if (_guarded_connection && !_new_connection) { + // Something bad happened, we need to close the current connection + // before returning it back to pool. + _guarded_connection->connection().invalidate(); + } +} + +template +void QueuedRedis::_rewrite_replies(std::vector &replies) const { + _rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies); + + _rewrite_replies(_empty_array_cmd_indexes, reply::rewrite_empty_array_reply, replies); +} + +template +template +void QueuedRedis::_rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const { + for (auto idx : indexes) { + assert(idx < replies.size()); + + auto &reply = replies[idx]; + + assert(reply); + + rewriter(*reply); + } +} + +inline std::size_t QueuedReplies::size() const { + return _replies.size(); +} + +inline redisReply& QueuedReplies::get(std::size_t idx) { + _index_check(idx); + + auto &reply = _replies[idx]; + + assert(reply); + + if (reply::is_error(*reply)) { + throw_error(*reply); + } + + return *reply; +} + +template +inline Result QueuedReplies::get(std::size_t idx) { + auto &reply = get(idx); + + return reply::parse(reply); +} + +template +inline void QueuedReplies::get(std::size_t idx, Output output) { + auto &reply = get(idx); + + reply::to_array(reply, output); +} + +inline void QueuedReplies::_index_check(std::size_t idx) const { + if (idx >= size()) { + throw Error("Out of range"); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis++.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis++.h new file mode 100644 index 000000000..0da0ebb16 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis++.h @@ -0,0 +1,25 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H +#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H + +#include "redis.h" +#include "redis_cluster.h" +#include "queued_redis.h" +#include "sentinel.h" + +#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis.h new file mode 100644 index 000000000..1333e6b68 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis.h @@ -0,0 +1,3631 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_H +#define SEWENEW_REDISPLUSPLUS_REDIS_H + +#include +#include +#include +#include +#include +#include "connection_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class Redis { +public: + /// @brief Construct `Redis` instance with connection options and connection pool options. + /// @param connection_opts Connection options. + /// @param pool_opts Connection pool options. + /// @see `ConnectionOptions` + /// @see `ConnectionPoolOptions` + /// @see https://github.com/sewenew/redis-plus-plus#connection + explicit Redis(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(std::make_shared(pool_opts, connection_opts)) {} + + /// @brief Construct `Redis` instance with URI. + /// @param uri URI, e.g. 'tcp://127.0.0.1', 'tcp://127.0.0.1:6379', or 'unix://path/to/socket'. + /// Full URI scheme: 'tcp://[[username:]password@]host[:port][/db]' or + /// unix://[[username:]password@]path-to-unix-domain-socket[/db] + /// @see https://github.com/sewenew/redis-plus-plus/issues/37 + /// @see https://github.com/sewenew/redis-plus-plus#connection + explicit Redis(const std::string &uri); + + /// @brief Construct `Redis` instance with Redis sentinel, i.e. get node info from sentinel. + /// @param sentinel `Sentinel` instance. + /// @param master_name Name of master node. + /// @param role Connect to master node or slave node. + /// - Role::MASTER: Connect to master node. + /// - Role::SLAVE: Connect to slave node. + /// @param connection_opts Connection options. + /// @param pool_opts Connection pool options. + /// @see `Sentinel` + /// @see `Role` + /// @see https://github.com/sewenew/redis-plus-plus#redis-sentinel + Redis(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role, + const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(std::make_shared(SimpleSentinel(sentinel, master_name, role), + pool_opts, + connection_opts)) {} + + /// @brief `Redis` is not copyable. + Redis(const Redis &) = delete; + + /// @brief `Redis` is not copyable. + Redis& operator=(const Redis &) = delete; + + /// @brief `Redis` is movable. + Redis(Redis &&) = default; + + /// @brief `Redis` is movable. + Redis& operator=(Redis &&) = default; + + /// @brief Create a pipeline. + /// @param new_connection Whether creating a `Pipeline` object in a new connection. + /// @return The created pipeline. + /// @note Instead of picking a connection from the underlying connection pool, + /// this method will create a new connection to Redis. So it's not a cheap operation, + /// and you'd better reuse the returned object as much as possible. + /// @see https://github.com/sewenew/redis-plus-plus#pipeline + Pipeline pipeline(bool new_connection = true); + + /// @brief Create a transaction. + /// @param piped Whether commands in a transaction should be sent in a pipeline to reduce RTT. + /// @param new_connection Whether creating a `Pipeline` object in a new connection. + /// @return The created transaction. + /// @note Instead of picking a connection from the underlying connection pool, + /// this method will create a new connection to Redis. So it's not a cheap operation, + /// and you'd better reuse the returned object as much as possible. + /// @see https://github.com/sewenew/redis-plus-plus#transaction + Transaction transaction(bool piped = false, bool new_connection = true); + + /// @brief Create a subscriber. + /// @return The created subscriber. + /// @note Instead of picking a connection from the underlying connection pool, + /// this method will create a new connection to Redis. So it's not a cheap operation, + /// and you'd better reuse the returned object as much as possible. + /// @see https://github.com/sewenew/redis-plus-plus#publishsubscribe + Subscriber subscriber(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type; + + template + Result command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // CONNECTION commands. + + /// @brief Send password to Redis. + /// @param password Password. + /// @note Normally, you should not call this method. + /// Instead, you should set password with `ConnectionOptions` or URI. + /// @see https://redis.io/commands/auth + void auth(const StringView &password); + + /// @brief Send user and password to Redis. + /// @param user User name. + /// @param password Password. + /// @note Normally, you should not call this method. + /// Instead, you should set password with `ConnectionOptions` or URI. + /// Also this overload only works with Redis 6.0 or later. + /// @see https://redis.io/commands/auth + void auth(const StringView &user, const StringView &password); + + /// @brief Ask Redis to return the given message. + /// @param msg Message to be sent. + /// @return Return the given message. + /// @see https://redis.io/commands/echo + std::string echo(const StringView &msg); + + /// @brief Test if the connection is alive. + /// @return Always return *PONG*. + /// @see https://redis.io/commands/ping + std::string ping(); + + /// @brief Test if the connection is alive. + /// @param msg Message sent to Redis. + /// @return Return the given message. + /// @see https://redis.io/commands/ping + std::string ping(const StringView &msg); + + // After sending QUIT, only the current connection will be close, while + // other connections in the pool is still open. This is a strange behavior. + // So we DO NOT support the QUIT command. If you want to quit connection to + // server, just destroy the Redis object. + // + // void quit(); + + // We get a connection from the pool, and send the SELECT command to switch + // to a specified DB. However, when we try to send other commands to the + // given DB, we might get a different connection from the pool, and these + // commands, in fact, work on other DB. e.g. + // + // redis.select(1); // get a connection from the pool and switch to the 1th DB + // redis.get("key"); // might get another connection from the pool, + // // and try to get 'key' on the default DB + // + // Obviously, this is NOT what we expect. So we DO NOT support SELECT command. + // In order to select a DB, we can specify the DB index with the ConnectionOptions. + // + // However, since Pipeline and Transaction always send multiple commands on a + // single connection, these two classes have a *select* method. + // + // void select(long long idx); + + /// @brief Swap two Redis databases. + /// @param idx1 The index of the first database. + /// @param idx2 The index of the second database. + /// @see https://redis.io/commands/swapdb + void swapdb(long long idx1, long long idx2); + + // SERVER commands. + + /// @brief Rewrite AOF in the background. + /// @see https://redis.io/commands/bgrewriteaof + void bgrewriteaof(); + + /// @brief Save database in the background. + /// @see https://redis.io/commands/bgsave + void bgsave(); + + /// @brief Get the size of the currently selected database. + /// @return Number of keys in currently selected database. + /// @see https://redis.io/commands/dbsize + long long dbsize(); + + /// @brief Remove keys of all databases. + /// @param async Whether flushing databases asynchronously, i.e. without blocking the server. + /// @see https://redis.io/commands/flushall + void flushall(bool async = false); + + /// @brief Remove keys of current databases. + /// @param async Whether flushing databases asynchronously, i.e. without blocking the server. + /// @see https://redis.io/commands/flushdb + void flushdb(bool async = false); + + /// @brief Get the info about the server. + /// @return Server info. + /// @see https://redis.io/commands/info + std::string info(); + + /// @brief Get the info about the server on the given section. + /// @param section Section. + /// @return Server info. + /// @see https://redis.io/commands/info + std::string info(const StringView §ion); + + /// @brief Get the UNIX timestamp in seconds, at which the database was saved successfully. + /// @return The last saving time. + /// @see https://redis.io/commands/lastsave + long long lastsave(); + + /// @brief Save databases into RDB file **synchronously**, i.e. block the server during saving. + /// @see https://redis.io/commands/save + void save(); + + // KEY commands. + + /// @brief Delete the given key. + /// @param key Key. + /// @return Number of keys removed. + /// @retval 1 If the key exists, and has been removed. + /// @retval 0 If the key does not exist. + /// @see https://redis.io/commands/del + long long del(const StringView &key); + + /// @brief Delete the given list of keys. + /// @param first Iterator to the first key in the range. + /// @param last Off-the-end iterator to the given range. + /// @return Number of keys removed. + /// @see https://redis.io/commands/del + template + long long del(Input first, Input last); + + /// @brief Delete the given list of keys. + /// @param il Initializer list of keys to be deleted. + /// @return Number of keys removed. + /// @see https://redis.io/commands/del + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + /// @brief Get the serialized valued stored at key. + /// @param key Key. + /// @return The serialized value. + /// @note If key does not exist, `dump` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/dump + OptionalString dump(const StringView &key); + + /// @brief Check if the given key exists. + /// @param key Key. + /// @return Whether the given key exists. + /// @retval 1 If key exists. + /// @retval 0 If key does not exist. + /// @see https://redis.io/commands/exists + long long exists(const StringView &key); + + /// @brief Check if the given keys exist. + /// @param first Iterator to the first key. + /// @param last Off-the-end iterator to the given range. + /// @return Number of keys existing. + /// @see https://redis.io/commands/exists + template + long long exists(Input first, Input last); + + /// @brief Check if the given keys exist. + /// @param il Initializer list of keys to be checked. + /// @return Number of keys existing. + /// @see https://redis.io/commands/exists + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + /// @brief Set a timeout on key. + /// @param key Key. + /// @param timeout Timeout in seconds. + /// @return Whether timeout has been set. + /// @retval true If timeout has been set. + /// @retval false If key does not exist. + /// @see https://redis.io/commands/expire + bool expire(const StringView &key, long long timeout); + + /// @brief Set a timeout on key. + /// @param key Key. + /// @param timeout Timeout in seconds. + /// @return Whether timeout has been set. + /// @retval true If timeout has been set. + /// @retval false If key does not exist. + /// @see https://redis.io/commands/expire + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + /// @brief Set a timeout on key, i.e. expire the key at a future time point. + /// @param key Key. + /// @param timestamp Time in seconds since UNIX epoch. + /// @return Whether timeout has been set. + /// @retval true If timeout has been set. + /// @retval false If key does not exist. + /// @see https://redis.io/commands/expireat + bool expireat(const StringView &key, long long timestamp); + + /// @brief Set a timeout on key, i.e. expire the key at a future time point. + /// @param key Key. + /// @param timestamp Time in seconds since UNIX epoch. + /// @return Whether timeout has been set. + /// @retval true If timeout has been set. + /// @retval false If key does not exist. + /// @see https://redis.io/commands/expireat + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + /// @brief Get keys matching the given pattern. + /// @param pattern Pattern. + /// @param output Output iterator to the destination where the returned keys are stored. + /// @note It's always a bad idea to call `keys`, since it might block Redis for a long time, + /// especially when the data set is very big. + /// @see `Redis::scan` + /// @see https://redis.io/commands/keys + template + void keys(const StringView &pattern, Output output); + + /// @brief Move a key to the given database. + /// @param key Key. + /// @param db The destination database. + /// @return Whether key has been moved. + /// @retval true If key has been moved. + /// @retval false If key was not moved. + /// @see https://redis.io/commands/move + bool move(const StringView &key, long long db); + + /// @brief Remove timeout on key. + /// @param key Key. + /// @return Whether timeout has been removed. + /// @retval true If timeout has been removed. + /// @retval false If key does not exist, or does not have an associated timeout. + /// @see https://redis.io/commands/persist + bool persist(const StringView &key); + + /// @brief Set a timeout on key. + /// @param key Key. + /// @param timeout Timeout in milliseconds. + /// @return Whether timeout has been set. + /// @retval true If timeout has been set. + /// @retval false If key does not exist. + /// @see https://redis.io/commands/pexpire + bool pexpire(const StringView &key, long long timeout); + + /// @brief Set a timeout on key. + /// @param key Key. + /// @param timeout Timeout in milliseconds. + /// @return Whether timeout has been set. + /// @retval true If timeout has been set. + /// @retval false If key does not exist. + /// @see https://redis.io/commands/pexpire + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + /// @brief Set a timeout on key, i.e. expire the key at a future time point. + /// @param key Key. + /// @param timestamp Time in milliseconds since UNIX epoch. + /// @return Whether timeout has been set. + /// @retval true If timeout has been set. + /// @retval false If key does not exist. + /// @see https://redis.io/commands/pexpireat + bool pexpireat(const StringView &key, long long timestamp); + + /// @brief Set a timeout on key, i.e. expire the key at a future time point. + /// @param key Key. + /// @param timestamp Time in milliseconds since UNIX epoch. + /// @return Whether timeout has been set. + /// @retval true If timeout has been set. + /// @retval false If key does not exist. + /// @see https://redis.io/commands/pexpireat + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + /// @brief Get the TTL of a key in milliseconds. + /// @param key Key. + /// @return TTL of the key in milliseconds. + /// @see https://redis.io/commands/pttl + long long pttl(const StringView &key); + + /// @brief Get a random key from current database. + /// @return A random key. + /// @note If the database is empty, `randomkey` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/randomkey + OptionalString randomkey(); + + /// @brief Rename `key` to `newkey`. + /// @param key Key to be renamed. + /// @param newkey The new name of the key. + /// @see https://redis.io/commands/rename + void rename(const StringView &key, const StringView &newkey); + + /// @brief Rename `key` to `newkey` if `newkey` does not exist. + /// @param key Key to be renamed. + /// @param newkey The new name of the key. + /// @return Whether key has been renamed. + /// @retval true If key has been renamed. + /// @retval false If newkey already exists. + /// @see https://redis.io/commands/renamenx + bool renamenx(const StringView &key, const StringView &newkey); + + /// @brief Create a key with the value obtained by `Redis::dump`. + /// @param key Key. + /// @param val Value obtained by `Redis::dump`. + /// @param ttl Timeout of the created key in milliseconds. If `ttl` is 0, set no timeout. + /// @param replace Whether to overwrite an existing key. + /// If `replace` is `true` and key already exists, throw an exception. + /// @see https://redis.io/commands/restore + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + /// @brief Create a key with the value obtained by `Redis::dump`. + /// @param key Key. + /// @param val Value obtained by `Redis::dump`. + /// @param ttl Timeout of the created key in milliseconds. If `ttl` is 0, set no timeout. + /// @param replace Whether to overwrite an existing key. + /// If `replace` is `true` and key already exists, throw an exception. + /// @see https://redis.io/commands/restore + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + /// @brief Scan keys of the database matching the given pattern. + /// + /// Example: + /// @code{.cpp} + /// auto cursor = 0LL; + /// std::unordered_set keys; + /// while (true) { + /// cursor = redis.scan(cursor, "pattern:*", 10, std::inserter(keys, keys.begin())); + /// if (cursor == 0) { + /// break; + /// } + /// } + /// @endcode + /// @param cursor Cursor. + /// @param pattern Pattern of the keys to be scanned. + /// @param count A hint for how many keys to be scanned. + /// @param output Output iterator to the destination where the returned keys are stored. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/scan + /// TODO: support the TYPE option for Redis 6.0. + template + long long scan(long long cursor, + const StringView &pattern, + long long count, + Output output); + + /// @brief Scan all keys of the database. + /// @param cursor Cursor. + /// @param output Output iterator to the destination where the returned keys are stored. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/scan + template + long long scan(long long cursor, + Output output); + + /// @brief Scan keys of the database matching the given pattern. + /// @param cursor Cursor. + /// @param pattern Pattern of the keys to be scanned. + /// @param output Output iterator to the destination where the returned keys are stored. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/scan + template + long long scan(long long cursor, + const StringView &pattern, + Output output); + + /// @brief Scan keys of the database matching the given pattern. + /// @param cursor Cursor. + /// @param count A hint for how many keys to be scanned. + /// @param output Output iterator to the destination where the returned keys are stored. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/scan + template + long long scan(long long cursor, + long long count, + Output output); + + /// @brief Update the last access time of the given key. + /// @param key Key. + /// @return Whether last access time of the key has been updated. + /// @retval 1 If key exists, and last access time has been updated. + /// @retval 0 If key does not exist. + /// @see https://redis.io/commands/touch + long long touch(const StringView &key); + + /// @brief Update the last access time of the given key. + /// @param first Iterator to the first key to be touched. + /// @param last Off-the-end iterator to the given range. + /// @return Whether last access time of the key has been updated. + /// @retval 1 If key exists, and last access time has been updated. + /// @retval 0 If key does not exist. + /// @see https://redis.io/commands/touch + template + long long touch(Input first, Input last); + + /// @brief Update the last access time of the given key. + /// @param il Initializer list of keys to be touched. + /// @return Whether last access time of the key has been updated. + /// @retval 1 If key exists, and last access time has been updated. + /// @retval 0 If key does not exist. + /// @see https://redis.io/commands/touch + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + /// @brief Get the remaining Time-To-Live of a key. + /// @param key Key. + /// @return TTL in seconds. + /// @retval TTL If the key has a timeout. + /// @retval -1 If the key exists but does not have a timeout. + /// @retval -2 If the key does not exist. + /// @note In Redis 2.6 or older, `ttl` returns -1 if the key does not exist, + /// or if the key exists but does not have a timeout. + /// @see https://redis.io/commands/ttl + long long ttl(const StringView &key); + + /// @brief Get the type of the value stored at key. + /// @param key Key. + /// @return The type of the value. + /// @see https://redis.io/commands/type + std::string type(const StringView &key); + + /// @brief Remove the given key asynchronously, i.e. without blocking Redis. + /// @param key Key. + /// @return Whether the key has been removed. + /// @retval 1 If key exists, and has been removed. + /// @retval 0 If key does not exist. + /// @see https://redis.io/commands/unlink + long long unlink(const StringView &key); + + /// @brief Remove the given keys asynchronously, i.e. without blocking Redis. + /// @param first Iterator to the first key in the given range. + /// @param last Off-the-end iterator to the given range. + /// @return Number of keys that have been removed. + /// @see https://redis.io/commands/unlink + template + long long unlink(Input first, Input last); + + /// @brief Remove the given keys asynchronously, i.e. without blocking Redis. + /// @param il Initializer list of keys to be unlinked. + /// @return Number of keys that have been removed. + /// @see https://redis.io/commands/unlink + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + /// @brief Wait until previous write commands are successfully replicated to at + /// least the specified number of replicas or the given timeout has been reached. + /// @param numslaves Number of replicas. + /// @param timeout Timeout in milliseconds. If timeout is 0ms, wait forever. + /// @return Number of replicas that have been successfully replicated these write commands. + /// @note The return value might be less than `numslaves`, because timeout has been reached. + /// @see https://redis.io/commands/wait + long long wait(long long numslaves, long long timeout); + + /// @brief Wait until previous write commands are successfully replicated to at + /// least the specified number of replicas or the given timeout has been reached. + /// @param numslaves Number of replicas. + /// @param timeout Timeout in milliseconds. If timeout is 0ms, wait forever. + /// @return Number of replicas that have been successfully replicated these write commands. + /// @note The return value might be less than `numslaves`, because timeout has been reached. + /// @see https://redis.io/commands/wait + /// TODO: Support default parameter for `timeout` to wait forever. + long long wait(long long numslaves, const std::chrono::milliseconds &timeout); + + // STRING commands. + + /// @brief Append the given string to the string stored at key. + /// @param key Key. + /// @param str String to be appended. + /// @return The length of the string after the append operation. + /// @see https://redis.io/commands/append + long long append(const StringView &key, const StringView &str); + + /// @brief Get the number of bits that have been set for the given range of the string. + /// @param key Key. + /// @param start Start index (inclusive) of the range. 0 means the beginning of the string. + /// @param end End index (inclusive) of the range. -1 means the end of the string. + /// @return Number of bits that have been set. + /// @note The index can be negative to index from the end of the string. + /// @see https://redis.io/commands/bitcount + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + /// @brief Do bit operation on the string stored at `key`, and save the result to `destination`. + /// @param op Bit operations. + /// @param destination The destination key where the result is saved. + /// @param key The key where the string to be operated is stored. + /// @return The length of the string saved at `destination`. + /// @see https://redis.io/commands/bitop + /// @see `BitOp` + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + /// @brief Do bit operations on the strings stored at the given keys, + /// and save the result to `destination`. + /// @param op Bit operations. + /// @param destination The destination key where the result is saved. + /// @param first Iterator to the first key where the string to be operated is stored. + /// @param last Off-the-end iterator to the given range of keys. + /// @return The length of the string saved at `destination`. + /// @see https://redis.io/commands/bitop + /// @see `BitOp` + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + /// @brief Do bit operations on the strings stored at the given keys, + /// and save the result to `destination`. + /// @param op Bit operations. + /// @param destination The destination key where the result is saved. + /// @param il Initializer list of keys where the strings are operated. + /// @return The length of the string saved at `destination`. + /// @see https://redis.io/commands/bitop + /// @see `BitOp` + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + /// @brief Get the position of the first bit set to 0 or 1 in the given range of the string. + /// @param key Key. + /// @param bit 0 or 1. + /// @param start Start index (inclusive) of the range. 0 means the beginning of the string. + /// @param end End index (inclusive) of the range. -1 means the end of the string. + /// @return The position of the first bit set to 0 or 1. + /// @see https://redis.io/commands/bitpos + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + /// @brief Decrement the integer stored at key by 1. + /// @param key Key. + /// @return The value after the decrement. + /// @see https://redis.io/commands/decr + long long decr(const StringView &key); + + /// @brief Decrement the integer stored at key by `decrement`. + /// @param key Key. + /// @param decrement Decrement. + /// @return The value after the decrement. + /// @see https://redis.io/commands/decrby + long long decrby(const StringView &key, long long decrement); + + /// @brief Get the string value stored at key. + /// + /// Example: + /// @code{.cpp} + /// auto val = redis.get("key"); + /// if (val) + /// std::cout << *val << std::endl; + /// else + /// std::cout << "key not exist" << std::endl; + /// @endcode + /// @param key Key. + /// @return The value stored at key. + /// @note If key does not exist, `get` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/get + OptionalString get(const StringView &key); + + /// @brief Get the bit value at offset in the string. + /// @param key Key. + /// @param offset Offset. + /// @return The bit value. + /// @see https://redis.io/commands/getbit + long long getbit(const StringView &key, long long offset); + + /// @brief Get the substring of the string stored at key. + /// @param key Key. + /// @param start Start index (inclusive) of the range. 0 means the beginning of the string. + /// @param end End index (inclusive) of the range. -1 means the end of the string. + /// @return The substring in range [start, end]. If key does not exist, return an empty string. + /// @see https://redis.io/commands/getrange + std::string getrange(const StringView &key, long long start, long long end); + + /// @brief Atomically set the string stored at `key` to `val`, and return the old value. + /// @param key Key. + /// @param val Value to be set. + /// @return The old value stored at key. + /// @note If key does not exist, `getset` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/getset + /// @see `OptionalString` + OptionalString getset(const StringView &key, const StringView &val); + + /// @brief Increment the integer stored at key by 1. + /// @param key Key. + /// @return The value after the increment. + /// @see https://redis.io/commands/incr + long long incr(const StringView &key); + + /// @brief Increment the integer stored at key by `increment`. + /// @param key Key. + /// @param increment Increment. + /// @return The value after the increment. + /// @see https://redis.io/commands/incrby + long long incrby(const StringView &key, long long increment); + + /// @brief Increment the floating point number stored at key by `increment`. + /// @param key Key. + /// @param increment Increment. + /// @return The value after the increment. + /// @see https://redis.io/commands/incrbyfloat + double incrbyfloat(const StringView &key, double increment); + + /// @brief Get the values of multiple keys atomically. + /// + /// Example: + /// @code{.cpp} + /// std::vector keys = {"k1", "k2", "k3"}; + /// std::vector vals; + /// redis.mget(keys.begin(), keys.end(), std::back_inserter(vals)); + /// for (const auto &val : vals) { + /// if (val) + /// std::cout << *val << std::endl; + /// else + /// std::cout << "key does not exist" << std::endl; + /// } + /// @endcode + /// @param first Iterator to the first key of the given range. + /// @param last Off-the-end iterator to the given range. + /// @param output Output iterator to the destination where the values are stored. + /// @note The destination should be a container of `OptionalString` type, + /// since the given key might not exist (in this case, the value of the corresponding + /// key is `OptionalString{}` (`std::nullopt`)). + /// @see https://redis.io/commands/mget + template + void mget(Input first, Input last, Output output); + + /// @brief Get the values of multiple keys atomically. + /// + /// Example: + /// @code{.cpp} + /// std::vector vals; + /// redis.mget({"k1", "k2", "k3"}, std::back_inserter(vals)); + /// for (const auto &val : vals) { + /// if (val) + /// std::cout << *val << std::endl; + /// else + /// std::cout << "key does not exist" << std::endl; + /// } + /// @endcode + /// @param il Initializer list of keys. + /// @param output Output iterator to the destination where the values are stored. + /// @note The destination should be a container of `OptionalString` type, + /// since the given key might not exist (in this case, the value of the corresponding + /// key is `OptionalString{}` (`std::nullopt`)). + /// @see https://redis.io/commands/mget + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + /// @brief Set multiple key-value pairs. + /// + /// Example: + /// @code{.cpp} + /// std::vector> kvs1 = {{"k1", "v1"}, {"k2", "v2"}}; + /// redis.mset(kvs1.begin(), kvs1.end()); + /// std::unordered_map kvs2 = {{"k3", "v3"}, {"k4", "v4"}}; + /// redis.mset(kvs2.begin(), kvs2.end()); + /// @endcode + /// @param first Iterator to the first key-value pair. + /// @param last Off-the-end iterator to the given range. + /// @see https://redis.io/commands/mset + template + void mset(Input first, Input last); + + /// @brief Set multiple key-value pairs. + /// + /// Example: + /// @code{.cpp} + /// redis.mset({std::make_pair("k1", "v1"), std::make_pair("k2", "v2")}); + /// @endcode + /// @param il Initializer list of key-value pairs. + /// @see https://redis.io/commands/mset + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + /// @brief Set the given key-value pairs if all specified keys do not exist. + /// + /// Example: + /// @code{.cpp} + /// std::vector> kvs1; + /// redis.msetnx(kvs1.begin(), kvs1.end()); + /// std::unordered_map kvs2; + /// redis.msetnx(kvs2.begin(), kvs2.end()); + /// @endcode + /// @param first Iterator to the first key-value pair. + /// @param last Off-the-end iterator of the given range. + /// @return Whether all keys have been set. + /// @retval true If all keys have been set. + /// @retval false If no key was set, i.e. at least one key already exist. + /// @see https://redis.io/commands/msetnx + template + bool msetnx(Input first, Input last); + + /// @brief Set the given key-value pairs if all specified keys do not exist. + /// + /// Example: + /// @code{.cpp} + /// redis.msetnx({make_pair("k1", "v1"), make_pair("k2", "v2")}); + /// @endcode + /// @param il Initializer list of key-value pairs. + /// @return Whether all keys have been set. + /// @retval true If all keys have been set. + /// @retval false If no key was set, i.e. at least one key already exist. + /// @see https://redis.io/commands/msetnx + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + /// @brief Set key-value pair with the given timeout in milliseconds. + /// @param key Key. + /// @param ttl Time-To-Live in milliseconds. + /// @param val Value. + /// @see https://redis.io/commands/psetex + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + /// @brief Set key-value pair with the given timeout in milliseconds. + /// @param key Key. + /// @param ttl Time-To-Live in milliseconds. + /// @param val Value. + /// @see https://redis.io/commands/psetex + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + /// @brief Set a key-value pair. + /// + /// Example: + /// @code{.cpp} + /// // Set a key-value pair. + /// redis.set("key", "value"); + /// // Set a key-value pair, and expire it after 10 seconds. + /// redis.set("key", "value", std::chrono::seconds(10)); + /// // Set a key-value pair with a timeout, only if the key already exists. + /// if (redis.set("key", "value", std::chrono::seconds(10), UpdateType::EXIST)) + /// std::cout << "OK" << std::endl; + /// else + /// std::cout << "key does not exist" << std::endl; + /// @endcode + /// @param key Key. + /// @param val Value. + /// @param ttl Timeout on the key. If `ttl` is 0ms, do not set timeout. + /// @param type Options for set command: + /// - UpdateType::EXIST: Set the key only if it already exists. + /// - UpdateType::NOT_EXIST: Set the key only if it does not exist. + /// - UpdateType::ALWAYS: Always set the key no matter whether it exists. + /// @return Whether the key has been set. + /// @retval true If the key has been set. + /// @retval false If the key was not set, because of the given option. + /// @see https://redis.io/commands/set + // TODO: Support KEEPTTL option for Redis 6.0 + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + // TODO: add SETBIT command. + + /// @brief Set key-value pair with the given timeout in seconds. + /// @param key Key. + /// @param ttl Time-To-Live in seconds. + /// @param val Value. + /// @see https://redis.io/commands/setex + void setex(const StringView &key, + long long ttl, + const StringView &val); + + /// @brief Set key-value pair with the given timeout in seconds. + /// @param key Key. + /// @param ttl Time-To-Live in seconds. + /// @param val Value. + /// @see https://redis.io/commands/setex + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + /// @brief Set the key if it does not exist. + /// @param key Key. + /// @param val Value. + /// @return Whether the key has been set. + /// @retval true If the key has been set. + /// @retval false If the key was not set, i.e. the key already exists. + /// @see https://redis.io/commands/setnx + bool setnx(const StringView &key, const StringView &val); + + /// @brief Set the substring starting from `offset` to the given value. + /// @param key Key. + /// @param offset Offset. + /// @param val Value. + /// @return The length of the string after this operation. + /// @see https://redis.io/commands/setrange + long long setrange(const StringView &key, long long offset, const StringView &val); + + /// @brief Get the length of the string stored at key. + /// @param key Key. + /// @return The length of the string. + /// @note If key does not exist, `strlen` returns 0. + /// @see https://redis.io/commands/strlen + long long strlen(const StringView &key); + + // LIST commands. + + /// @brief Pop the first element of the list in a blocking way. + /// @param key Key where the list is stored. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If list is empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::lpop` + /// @see https://redis.io/commands/blpop + OptionalStringPair blpop(const StringView &key, long long timeout); + + /// @brief Pop the first element of the list in a blocking way. + /// @param key Key where the list is stored. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If list is empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::lpop` + /// @see https://redis.io/commands/blpop + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + /// @brief Pop the first element of multiple lists in a blocking way. + /// @param first Iterator to the first key. + /// @param last Off-the-end iterator to the key range. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If all lists are empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::lpop` + /// @see https://redis.io/commands/blpop + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + /// @brief Pop the first element of multiple lists in a blocking way. + /// @param il Initializer list of keys. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If all lists are empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::lpop` + /// @see https://redis.io/commands/blpop + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + /// @brief Pop the first element of multiple lists in a blocking way. + /// @param first Iterator to the first key. + /// @param last Off-the-end iterator to the key range. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If all lists are empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::lpop` + /// @see https://redis.io/commands/blpop + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + /// @brief Pop the first element of multiple lists in a blocking way. + /// @param il Initializer list of keys. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If all lists are empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::lpop` + /// @see https://redis.io/commands/blpop + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + /// @brief Pop the last element of the list in a blocking way. + /// @param key Key where the list is stored. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If list is empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::rpop` + /// @see https://redis.io/commands/brpop + OptionalStringPair brpop(const StringView &key, long long timeout); + + /// @brief Pop the last element of the list in a blocking way. + /// @param key Key where the list is stored. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If list is empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::rpop` + /// @see https://redis.io/commands/brpop + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + /// @brief Pop the last element of multiple lists in a blocking way. + /// @param first Iterator to the first key. + /// @param last Off-the-end iterator to the key range. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If all lists are empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::rpop` + /// @see https://redis.io/commands/brpop + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + /// @brief Pop the last element of multiple lists in a blocking way. + /// @param il Initializer list of lists. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If all lists are empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::rpop` + /// @see https://redis.io/commands/brpop + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + /// @brief Pop the last element of multiple lists in a blocking way. + /// @param first Iterator to the first list. + /// @param last Off-the-end iterator to the list range. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If all lists are empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::rpop` + /// @see https://redis.io/commands/brpop + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + /// @brief Pop the last element of multiple lists in a blocking way. + /// @param il Initializer list of list keys. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-element pair. + /// @note If all lists are empty and timeout reaches, return `OptionalStringPair{}` (`std::nullopt`). + /// @see `Redis::rpop` + /// @see https://redis.io/commands/brpop + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + /// @brief Pop last element of one list and push it to the left of another list in blocking way. + /// @param source Key of the source list. + /// @param destination Key of the destination list. + /// @param timeout Timeout. 0 means block forever. + /// @return The popped element. + /// @note If the source list does not exist, `brpoplpush` returns `OptionalString{}` (`std::nullopt`). + /// @see `Redis::rpoplpush` + /// @see https://redis.io/commands/brpoplpush + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + /// @brief Pop last element of one list and push it to the left of another list in blocking way. + /// @param source Key of the source list. + /// @param destination Key of the destination list. + /// @param timeout Timeout. 0 means block forever. + /// @return The popped element. + /// @note If the source list does not exist, `brpoplpush` returns `OptionalString{}` (`std::nullopt`). + /// @see `Redis::rpoplpush` + /// @see https://redis.io/commands/brpoplpush + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + /// @brief Get the element at the given index of the list. + /// @param key Key where the list is stored. + /// @param index Zero-base index, and -1 means the last element. + /// @return The element at the given index. + /// @see https://redis.io/commands/lindex + OptionalString lindex(const StringView &key, long long index); + + /// @brief Insert an element to a list before or after the pivot element. + /// + /// Example: + /// @code{.cpp} + /// // Insert 'hello' before 'world' + /// auto len = redis.linsert("list", InsertPosition::BEFORE, "world", "hello"); + /// if (len == -1) + /// std::cout << "there's no 'world' in the list" << std::endl; + /// else + /// std::cout << "after the operation, the length of the list is " << len << std::endl; + /// @endcode + /// @param key Key where the list is stored. + /// @param position Before or after the pivot element. + /// @param pivot The pivot value. The `pivot` is the value of the element, not the index. + /// @param val Element to be inserted. + /// @return The length of the list after the operation. + /// @note If the pivot value is not found, `linsert` returns -1. + /// @see `InsertPosition` + /// @see https://redis.io/commands/linsert + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + /// @brief Get the length of the list. + /// @param key Key where the list is stored. + /// @return The length of the list. + /// @see https://redis.io/commands/llen + long long llen(const StringView &key); + + /// @brief Pop the first element of the list. + /// + /// Example: + /// @code{.cpp} + /// auto element = redis.lpop("list"); + /// if (element) + /// std::cout << *element << std::endl; + /// else + /// std::cout << "list is empty, i.e. list does not exist" << std::endl; + /// @endcode + /// @param key Key where the list is stored. + /// @return The popped element. + /// @note If list is empty, i.e. key does not exist, return `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/lpop + OptionalString lpop(const StringView &key); + + /// @brief Push an element to the beginning of the list. + /// @param key Key where the list is stored. + /// @param val Element to be pushed. + /// @return The length of the list after the operation. + /// @see https://redis.io/commands/lpush + long long lpush(const StringView &key, const StringView &val); + + /// @brief Push multiple elements to the beginning of the list. + /// + /// Example: + /// @code{.cpp} + /// std::vector elements = {"e1", "e2", "e3"}; + /// redis.lpush("list", elements.begin(), elements.end()); + /// @endcode + /// @param key Key where the list is stored. + /// @param first Iterator to the first element to be pushed. + /// @param last Off-the-end iterator to the given element range. + /// @return The length of the list after the operation. + /// @see https://redis.io/commands/lpush + template + long long lpush(const StringView &key, Input first, Input last); + + /// @brief Push multiple elements to the beginning of the list. + /// + /// Example: + /// @code{.cpp} + /// redis.lpush("list", {"e1", "e2", "e3"}); + /// @endcode + /// @param key Key where the list is stored. + /// @param il Initializer list of elements. + /// @return The length of the list after the operation. + /// @see https://redis.io/commands/lpush + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + /// @brief Push an element to the beginning of the list, only if the list already exists. + /// @param key Key where the list is stored. + /// @param val Element to be pushed. + /// @return The length of the list after the operation. + /// @see https://redis.io/commands/lpushx + // TODO: add a multiple elements overload. + long long lpushx(const StringView &key, const StringView &val); + + /// @brief Get elements in the given range of the given list. + /// + /// Example: + /// @code{.cpp} + /// std::vector elements; + /// // Save all elements of a Redis list to a vector of string. + /// redis.lrange("list", 0, -1, std::back_inserter(elements)); + /// @endcode + /// @param key Key where the list is stored. + /// @param start Start index of the range. Index can be negative, which mean index from the end. + /// @param stop End index of the range. + /// @param output Output iterator to the destination where the results are saved. + /// @see https://redis.io/commands/lrange + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + /// @brief Remove the first `count` occurrences of elements equal to `val`. + /// @param key Key where the list is stored. + /// @param count Number of occurrences to be removed. + /// @param val Value. + /// @return Number of elements removed. + /// @note `count` can be positive, negative and 0. Check the reference for detail. + /// @see https://redis.io/commands/lrem + long long lrem(const StringView &key, long long count, const StringView &val); + + /// @brief Set the element at the given index to the specified value. + /// @param key Key where the list is stored. + /// @param index Index of the element to be set. + /// @param val Value. + /// @see https://redis.io/commands/lset + void lset(const StringView &key, long long index, const StringView &val); + + /// @brief Trim a list to keep only element in the given range. + /// @param key Key where the key is stored. + /// @param start Start of the index. + /// @param stop End of the index. + /// @see https://redis.io/commands/ltrim + void ltrim(const StringView &key, long long start, long long stop); + + /// @brief Pop the last element of a list. + /// @param key Key where the list is stored. + /// @return The popped element. + /// @note If the list is empty, i.e. key does not exist, `rpop` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/rpop + OptionalString rpop(const StringView &key); + + /// @brief Pop last element of one list and push it to the left of another list. + /// @param source Key of the source list. + /// @param destination Key of the destination list. + /// @return The popped element. + /// @note If the source list does not exist, `rpoplpush` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/brpoplpush + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + /// @brief Push an element to the end of the list. + /// @param key Key where the list is stored. + /// @param val Element to be pushed. + /// @return The length of the list after the operation. + /// @see https://redis.io/commands/rpush + long long rpush(const StringView &key, const StringView &val); + + /// @brief Push multiple elements to the end of the list. + /// @param key Key where the list is stored. + /// @param first Iterator to the first element to be pushed. + /// @param last Off-the-end iterator to the given element range. + /// @return The length of the list after the operation. + /// @see https://redis.io/commands/rpush + template + long long rpush(const StringView &key, Input first, Input last); + + /// @brief Push multiple elements to the end of the list. + /// @param key Key where the list is stored. + /// @param il Initializer list of elements to be pushed. + /// @return The length of the list after the operation. + /// @see https://redis.io/commands/rpush + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + /// @brief Push an element to the end of the list, only if the list already exists. + /// @param key Key where the list is stored. + /// @param val Element to be pushed. + /// @return The length of the list after the operation. + /// @see https://redis.io/commands/rpushx + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + /// @brief Remove the given field from hash. + /// @param key Key where the hash is stored. + /// @param field Field to be removed. + /// @return Whether the field has been removed. + /// @retval 1 If the field exists, and has been removed. + /// @retval 0 If the field does not exist. + /// @see https://redis.io/commands/hdel + long long hdel(const StringView &key, const StringView &field); + + /// @brief Remove multiple fields from hash. + /// @param key Key where the hash is stored. + /// @param first Iterator to the first field to be removed. + /// @param last Off-the-end iterator to the given field range. + /// @return Number of fields that has been removed. + /// @see https://redis.io/commands/hdel + template + long long hdel(const StringView &key, Input first, Input last); + + /// @brief Remove multiple fields from hash. + /// @param key Key where the hash is stored. + /// @param il Initializer list of fields. + /// @return Number of fields that has been removed. + /// @see https://redis.io/commands/hdel + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + /// @brief Check if the given field exists in hash. + /// @param key Key where the hash is stored. + /// @param field Field. + /// @return Whether the field exists. + /// @retval true If the field exists in hash. + /// @retval false If the field does not exist. + /// @see https://redis.io/commands/hexists + bool hexists(const StringView &key, const StringView &field); + + /// @brief Get the value of the given field. + /// @param key Key where the hash is stored. + /// @param field Field. + /// @return Value of the given field. + /// @note If field does not exist, `hget` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/hget + OptionalString hget(const StringView &key, const StringView &field); + + /// @brief Get all field-value pairs of the given hash. + /// + /// Example: + /// @code{.cpp} + /// std::unordered_map results; + /// // Save all field-value pairs of a Redis hash to an unordered_map. + /// redis.hgetall("hash", std::inserter(results, results.begin())); + /// @endcode + /// @param key Key where the hash is stored. + /// @param output Output iterator to the destination where the result is saved. + /// @note It's always a bad idea to call `hgetall` on a large hash, since it will block Redis. + /// @see `Redis::hscan` + /// @see https://redis.io/commands/hgetall + template + void hgetall(const StringView &key, Output output); + + /// @brief Increment the integer stored at the given field. + /// @param key Key where the hash is stored. + /// @param field Field. + /// @param increment Increment. + /// @return The value of the field after the increment. + /// @see https://redis.io/commands/hincrby + long long hincrby(const StringView &key, const StringView &field, long long increment); + + /// @brief Increment the floating point number stored at the given field. + /// @param key Key where the hash is stored. + /// @param field Field. + /// @param increment Increment. + /// @return The value of the field after the increment. + /// @see https://redis.io/commands/hincrbyfloat + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + /// @brief Get all fields of the given hash. + /// @param key Key where the hash is stored. + /// @param output Output iterator to the destination where the result is saved. + /// @note It's always a bad idea to call `hkeys` on a large hash, since it will block Redis. + /// @see `Redis::hscan` + /// @see https://redis.io/commands/hkeys + template + void hkeys(const StringView &key, Output output); + + /// @brief Get the number of fields of the given hash. + /// @param key Key where the hash is stored. + /// @return Number of fields. + /// @see https://redis.io/commands/hlen + long long hlen(const StringView &key); + + /// @brief Get values of multiple fields. + /// + /// Example: + /// @code{.cpp} + /// std::vector fields = {"f1", "f2"}; + /// std::vector vals; + /// redis.hmget("hash", fields.begin(), fields.end(), std::back_inserter(vals)); + /// for (const auto &val : vals) { + /// if (val) + /// std::cout << *val << std::endl; + /// else + /// std::cout << "field not exist" << std::endl; + /// } + /// @endcode + /// @param key Key where the hash is stored. + /// @param first Iterator to the first field. + /// @param last Off-the-end iterator to the given field range. + /// @param output Output iterator to the destination where the result is saved. + /// @note The destination should be a container of `OptionalString` type, + /// since the given field might not exist (in this case, the value of the corresponding + /// field is `OptionalString{}` (`std::nullopt`)). + /// @see https://redis.io/commands/hmget + template + void hmget(const StringView &key, Input first, Input last, Output output); + + /// @brief Get values of multiple fields. + /// + /// Example: + /// @code{.cpp} + /// std::vector vals; + /// redis.hmget("hash", {"f1", "f2"}, std::back_inserter(vals)); + /// for (const auto &val : vals) { + /// if (val) + /// std::cout << *val << std::endl; + /// else + /// std::cout << "field not exist" << std::endl; + /// } + /// @endcode + /// @param key Key where the hash is stored. + /// @param il Initializer list of fields. + /// @param output Output iterator to the destination where the result is saved. + /// @note The destination should be a container of `OptionalString` type, + /// since the given field might not exist (in this case, the value of the corresponding + /// field is `OptionalString{}` (`std::nullopt`)). + /// @see https://redis.io/commands/hmget + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + /// @brief Set multiple field-value pairs of the given hash. + /// + /// Example: + /// @code{.cpp} + /// std::unordered_map m = {{"f1", "v1"}, {"f2", "v2"}}; + /// redis.hmset("hash", m.begin(), m.end()); + /// @endcode + /// @param key Key where the hash is stored. + /// @param first Iterator to the first field-value pair. + /// @param last Off-the-end iterator to the range. + /// @see https://redis.io/commands/hmset + template + void hmset(const StringView &key, Input first, Input last); + + /// @brief Set multiple field-value pairs of the given hash. + /// + /// Example: + /// @code{.cpp} + /// redis.hmset("hash", {std::make_pair("f1", "v1"), std::make_pair("f2", "v2")}); + /// @endcode + /// @param key Key where the hash is stored. + /// @param il Initializer list of field-value pairs. + /// @see https://redis.io/commands/hmset + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + /// @brief Scan fields of the given hash matching the given pattern. + /// + /// Example: + /// @code{.cpp} + /// auto cursor = 0LL; + /// std::unordered_map kvs; + /// while (true) { + /// cursor = redis.hscan("hash", cursor, "pattern:*", 10, std::inserter(kvs, kvs.begin())); + /// if (cursor == 0) { + /// break; + /// } + /// } + /// @endcode + /// @param key Key where the hash is stored. + /// @param cursor Cursor. + /// @param pattern Pattern of fields to be scanned. + /// @param count A hint for how many fields to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/hscan + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + /// @brief Scan fields of the given hash matching the given pattern. + /// @param key Key where the hash is stored. + /// @param cursor Cursor. + /// @param pattern Pattern of fields to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/hscan + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + /// @brief Scan all fields of the given hash. + /// @param key Key where the hash is stored. + /// @param cursor Cursor. + /// @param count A hint for how many fields to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/hscan + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + /// @brief Scan all fields of the given hash. + /// @param key Key where the hash is stored. + /// @param cursor Cursor. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/hscan + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + /// @brief Set hash field to value. + /// @param key Key where the hash is stored. + /// @param field Field. + /// @param val Value. + /// @return Whether the given field is a new field. + /// @retval true If the given field didn't exist, and a new field has been added. + /// @retval false If the given field already exists, and its value has been overwritten. + /// @note When `hset` returns false, it does not mean that the method failed to set the field. + /// Instead, it means that the field already exists, and we've overwritten its value. + /// If `hset` fails, it will throw an exception of `Exception` type. + /// @see https://github.com/sewenew/redis-plus-plus/issues/9 + /// @see https://redis.io/commands/hset + bool hset(const StringView &key, const StringView &field, const StringView &val); + + /// @brief Set hash field to value. + /// @param key Key where the hash is stored. + /// @param item The field-value pair to be set. + /// @return Whether the given field is a new field. + /// @retval true If the given field didn't exist, and a new field has been added. + /// @retval false If the given field already exists, and its value has been overwritten. + /// @note When `hset` returns false, it does not mean that the method failed to set the field. + /// Instead, it means that the field already exists, and we've overwritten its value. + /// If `hset` fails, it will throw an exception of `Exception` type. + /// @see https://github.com/sewenew/redis-plus-plus/issues/9 + /// @see https://redis.io/commands/hset + bool hset(const StringView &key, const std::pair &item); + + /// @brief Set multiple fields of the given hash. + /// + /// Example: + /// @code{.cpp} + /// std::unordered_map m = {{"f1", "v1"}, {"f2", "v2"}}; + /// redis.hset("hash", m.begin(), m.end()); + /// @endcode + /// @param key Key where the hash is stored. + /// @param first Iterator to the first field to be set. + /// @param last Off-the-end iterator to the given range. + /// @return Number of fields that have been added, i.e. fields that not existed before. + /// @see https://redis.io/commands/hset + template + auto hset(const StringView &key, Input first, Input last) + -> typename std::enable_if::value, long long>::type; + + /// @brief Set multiple fields of the given hash. + /// + /// Example: + /// @code{.cpp} + /// redis.hset("hash", {std::make_pair("f1", "v1"), std::make_pair("f2", "v2")}); + /// @endcode + /// @param key Key where the hash is stored. + /// @param il Initializer list of field-value pairs. + /// @return Number of fields that have been added, i.e. fields that not existed before. + /// @see https://redis.io/commands/hset + template + long long hset(const StringView &key, std::initializer_list il) { + return hset(key, il.begin(), il.end()); + } + + /// @brief Set hash field to value, only if the given field does not exist. + /// @param key Key where the hash is stored. + /// @param field Field. + /// @param val Value. + /// @return Whether the field has been set. + /// @retval true If the field has been set. + /// @retval false If failed to set the field, i.e. the field already exists. + /// @see https://redis.io/commands/hsetnx + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + /// @brief Set hash field to value, only if the given field does not exist. + /// @param key Key where the hash is stored. + /// @param item The field-value pair to be set. + /// @return Whether the field has been set. + /// @retval true If the field has been set. + /// @retval false If failed to set the field, i.e. the field already exists. + /// @see https://redis.io/commands/hsetnx + bool hsetnx(const StringView &key, const std::pair &item); + + /// @brief Get the length of the string stored at the given field. + /// @param key Key where the hash is stored. + /// @param field Field. + /// @return Length of the string. + /// @see https://redis.io/commands/hstrlen + long long hstrlen(const StringView &key, const StringView &field); + + /// @brief Get values of all fields stored at the given hash. + /// @param key Key where the hash is stored. + /// @param output Output iterator to the destination where the result is saved. + /// @note It's always a bad idea to call `hvals` on a large hash, since it might block Redis. + /// @see `Redis::hscan` + /// @see https://redis.io/commands/hvals + template + void hvals(const StringView &key, Output output); + + // SET commands. + + /// @brief Add a member to the given set. + /// @param key Key where the set is stored. + /// @param member Member to be added. + /// @return Whether the given member is a new member. + /// @retval 1 The member did not exist before, and it has been added now. + /// @retval 0 The member already exists before this operation. + /// @see https://redis.io/commands/sadd + long long sadd(const StringView &key, const StringView &member); + + /// @brief Add multiple members to the given set. + /// @param key Key where the set is stored. + /// @param first Iterator to the first member to be added. + /// @param last Off-the-end iterator to the member range. + /// @return Number of new members that have been added, i.e. members did not exist before. + /// @see https://redis.io/commands/sadd + template + long long sadd(const StringView &key, Input first, Input last); + + /// @brief Add multiple members to the given set. + /// @param key Key where the set is stored. + /// @param il Initializer list of members to be added. + /// @return Number of new members that have been added, i.e. members did not exist before. + /// @see https://redis.io/commands/sadd + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + /// @brief Get the number of members in the set. + /// @param key Key where the set is stored. + /// @return Number of members. + /// @see https://redis.io/commands/scard + long long scard(const StringView &key); + + /// @brief Get the difference between the first set and all successive sets. + /// @param first Iterator to the first set. + /// @param last Off-the-end iterator to the range. + /// @param output Output iterator to the destination where the result is saved. + /// @see https://redis.io/commands/sdiff + // TODO: `void sdiff(const StringView &key, Input first, Input last, Output output)` is better. + template + void sdiff(Input first, Input last, Output output); + + /// @brief Get the difference between the first set and all successive sets. + /// @param il Initializer list of sets. + /// @param output Output iterator to the destination where the result is saved. + /// @see https://redis.io/commands/sdiff + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + /// @brief Copy set stored at `key` to `destination`. + /// @param destination Key of the destination set. + /// @param key Key of the source set. + /// @return Number of members of the set. + /// @see https://redis.io/commands/sdiffstore + long long sdiffstore(const StringView &destination, const StringView &key); + + /// @brief Same as `sdiff`, except that it stores the result to another set. + /// @param destination Key of the destination set. + /// @param first Iterator to the first set. + /// @param last Off-the-end iterator to set range. + /// @return Number of members in the resulting set. + /// @see https://redis.io/commands/sdiffstore + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + /// @brief Same as `sdiff`, except that it stores the result to another set. + /// @param destination Key of the destination set. + /// @param il Initializer list of sets. + /// @return Number of members in the resulting set. + /// @see https://redis.io/commands/sdiffstore + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + /// @brief Get the intersection between the first set and all successive sets. + /// @param first Iterator to the first set. + /// @param last Off-the-end iterator to the range. + /// @param output Output iterator to the destination where the result is saved. + /// @see https://redis.io/commands/sinter + // TODO: `void sinter(const StringView &key, Input first, Input last, Output output)` is better. + template + void sinter(Input first, Input last, Output output); + + /// @brief Get the intersection between the first set and all successive sets. + /// @param il Initializer list of sets. + /// @param output Output iterator to the destination where the result is saved. + /// @see https://redis.io/commands/sinter + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + /// @brief Copy set stored at `key` to `destination`. + /// @param destination Key of the destination set. + /// @param key Key of the source set. + /// @return Number of members of the set. + /// @see https://redis.io/commands/sinter + long long sinterstore(const StringView &destination, const StringView &key); + + /// @brief Same as `sinter`, except that it stores the result to another set. + /// @param destination Key of the destination set. + /// @param first Iterator to the first set. + /// @param last Off-the-end iterator to set range. + /// @return Number of members in the resulting set. + /// @see https://redis.io/commands/sinter + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + /// @brief Same as `sinter`, except that it stores the result to another set. + /// @param destination Key of the destination set. + /// @param il Initializer list of sets. + /// @return Number of members in the resulting set. + /// @see https://redis.io/commands/sinter + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + /// @brief Test if `member` exists in the set stored at key. + /// @param key Key where the set is stored. + /// @param member Member to be checked. + /// @return Whether `member` exists in the set. + /// @retval true If it exists in the set. + /// @retval false If it does not exist in the set, or the given key does not exist. + /// @see https://redis.io/commands/sismember + bool sismember(const StringView &key, const StringView &member); + + /// @brief Get all members in the given set. + /// + /// Example: + /// @code{.cpp} + /// std::unordered_set members1; + /// redis.smembers("set", std::inserter(members1, members1.begin())); + /// std::vector members2; + /// redis.smembers("set", std::back_inserter(members2)); + /// @endcode + /// @param key Key where the set is stored. + /// @param output Iterator to the destination where the result is saved. + /// @see https://redis.io/commands/smembers + template + void smembers(const StringView &key, Output output); + + /// @brief Move `member` from one set to another. + /// @param source Key of the set in which the member currently exists. + /// @param destination Key of the destination set. + /// @return Whether the member has been moved. + /// @retval true If the member has been moved. + /// @retval false If `member` does not exist in `source`. + /// @see https://redis.io/commands/smove + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + /// @brief Remove a random member from the set. + /// @param key Key where the set is stored. + /// @return The popped member. + /// @note If the set is empty, `spop` returns `OptionalString{}` (`std::nullopt`). + /// @see `Redis::srandmember` + /// @see https://redis.io/commands/spop + OptionalString spop(const StringView &key); + + /// @brief Remove multiple random members from the set. + /// + /// Example: + /// @code{.cpp} + /// std::vector members; + /// redis.spop("set", 10, std::back_inserter(members)); + /// @endcode + /// @param key Key where the set is stored. + /// @param count Number of members to be popped. + /// @param output Output iterator to the destination where the result is saved. + /// @note The number of popped members might be less than `count`. + /// @see `Redis::srandmember` + /// @see https://redis.io/commands/spop + template + void spop(const StringView &key, long long count, Output output); + + /// @brief Get a random member of the given set. + /// @param key Key where the set is stored. + /// @return A random member. + /// @note If the set is empty, `srandmember` returns `OptionalString{}` (`std::nullopt`). + /// @note This method won't remove the member from the set. + /// @see `Redis::spop` + /// @see https://redis.io/commands/srandmember + OptionalString srandmember(const StringView &key); + + /// @brief Get multiple random members of the given set. + /// @param key Key where the set is stored. + /// @param count Number of members to be returned. + /// @param output Output iterator to the destination where the result is saved. + /// @note This method won't remove members from the set. + /// @see `Redis::spop` + /// @see https://redis.io/commands/srandmember + template + void srandmember(const StringView &key, long long count, Output output); + + /// @brief Remove a member from set. + /// @param key Key where the set is stored. + /// @param member Member to be removed. + /// @return Whether the member has been removed. + /// @retval 1 If the given member exists, and has been removed. + /// @retval 0 If the given member does not exist. + /// @see https://redis.io/commands/srem + long long srem(const StringView &key, const StringView &member); + + /// @brief Remove multiple members from set. + /// @param key Key where the set is stored. + /// @param first Iterator to the first member to be removed. + /// @param last Off-the-end iterator to the range. + /// @return Number of members that have been removed. + /// @see https://redis.io/commands/srem + template + long long srem(const StringView &key, Input first, Input last); + + /// @brief Remove multiple members from set. + /// @param key Key where the set is stored. + /// @param il Initializer list of members to be removed. + /// @return Number of members that have been removed. + /// @see https://redis.io/commands/srem + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + /// @brief Scan members of the set matching the given pattern. + /// + /// Example: + /// @code{.cpp} + /// auto cursor = 0LL; + /// std::unordered_set members; + /// while (true) { + /// cursor = redis.sscan("set", cursor, "pattern:*", + /// 10, std::inserter(members, members.begin())); + /// if (cursor == 0) { + /// break; + /// } + /// } + /// @endcode + /// @param key Key where the set is stored. + /// @param cursor Cursor. + /// @param pattern Pattern of fields to be scanned. + /// @param count A hint for how many fields to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/sscan + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + /// @brief Scan members of the set matching the given pattern. + /// @param key Key where the set is stored. + /// @param cursor Cursor. + /// @param pattern Pattern of fields to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/sscan + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + /// @brief Scan all members of the given set. + /// @param key Key where the set is stored. + /// @param cursor Cursor. + /// @param count A hint for how many fields to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/sscan + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + /// @brief Scan all members of the given set. + /// @param key Key where the set is stored. + /// @param cursor Cursor. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/sscan + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + /// @brief Get the union between the first set and all successive sets. + /// @param first Iterator to the first set. + /// @param last Off-the-end iterator to the range. + /// @param output Output iterator to the destination where the result is saved. + /// @see https://redis.io/commands/sunion + // TODO: `void sunion(const StringView &key, Input first, Input last, Output output)` is better. + template + void sunion(Input first, Input last, Output output); + + /// @brief Get the union between the first set and all successive sets. + /// @param il Initializer list of sets. + /// @param output Output iterator to the destination where the result is saved. + /// @see https://redis.io/commands/sunion + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + /// @brief Copy set stored at `key` to `destination`. + /// @param destination Key of the destination set. + /// @param key Key of the source set. + /// @return Number of members of the set. + /// @see https://redis.io/commands/sunionstore + long long sunionstore(const StringView &destination, const StringView &key); + + /// @brief Same as `sunion`, except that it stores the result to another set. + /// @param destination Key of the destination set. + /// @param first Iterator to the first set. + /// @param last Off-the-end iterator to set range. + /// @return Number of members in the resulting set. + /// @see https://redis.io/commands/sunionstore + template + long long sunionstore(const StringView &destination, Input first, Input last); + + /// @brief Same as `sunion`, except that it stores the result to another set. + /// @param destination Key of the destination set. + /// @param il Initializer list of sets. + /// @return Number of members in the resulting set. + /// @see https://redis.io/commands/sunionstore + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + /// @brief Pop the member with highest score from sorted set in a blocking way. + /// @param key Key where the sorted set is stored. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the highest score. + /// @note If sorted set is empty and timeout reaches, `bzpopmax` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmax` + /// @see https://redis.io/commands/bzpopmax + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + /// @brief Pop the member with highest score from sorted set in a blocking way. + /// @param key Key where the sorted set is stored. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the highest score. + /// @note If sorted set is empty and timeout reaches, `bzpopmax` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmax` + /// @see https://redis.io/commands/bzpopmax + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + /// @brief Pop the member with highest score from multiple sorted set in a blocking way. + /// @param first Iterator to the first key. + /// @param last Off-the-end iterator to the key range. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the higest score. + /// @note If all lists are empty and timeout reaches, `bzpopmax` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmax` + /// @see https://redis.io/commands/bzpopmax + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + /// @brief Pop the member with highest score from multiple sorted set in a blocking way. + /// @param first Iterator to the first key. + /// @param last Off-the-end iterator to the key range. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the higest score. + /// @note If all lists are empty and timeout reaches, `bzpopmax` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmax` + /// @see https://redis.io/commands/bzpopmax + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + /// @brief Pop the member with highest score from multiple sorted set in a blocking way. + /// @param il Initializer list of sorted sets. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the higest score. + /// @note If all lists are empty and timeout reaches, `bzpopmax` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmax` + /// @see https://redis.io/commands/bzpopmax + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + /// @brief Pop the member with highest score from multiple sorted set in a blocking way. + /// @param il Initializer list of sorted sets. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the higest score. + /// @note If all lists are empty and timeout reaches, `bzpopmax` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmax` + /// @see https://redis.io/commands/bzpopmax + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + /// @brief Pop the member with lowest score from sorted set in a blocking way. + /// @param key Key where the sorted set is stored. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the lowest score. + /// @note If sorted set is empty and timeout reaches, `bzpopmin` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmin` + /// @see https://redis.io/commands/bzpopmin + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + /// @brief Pop the member with lowest score from sorted set in a blocking way. + /// @param key Key where the sorted set is stored. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the lowest score. + /// @note If sorted set is empty and timeout reaches, `bzpopmin` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmin` + /// @see https://redis.io/commands/bzpopmin + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + /// @brief Pop the member with lowest score from multiple sorted set in a blocking way. + /// @param first Iterator to the first key. + /// @param last Off-the-end iterator to the key range. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the lowest score. + /// @note If all lists are empty and timeout reaches, `bzpopmin` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmin` + /// @see https://redis.io/commands/bzpopmin + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + /// @brief Pop the member with lowest score from multiple sorted set in a blocking way. + /// @param first Iterator to the first key. + /// @param last Off-the-end iterator to the key range. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the lowest score. + /// @note If all lists are empty and timeout reaches, `bzpopmin` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmin` + /// @see https://redis.io/commands/bzpopmin + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + /// @brief Pop the member with lowest score from multiple sorted set in a blocking way. + /// @param il Initializer list of sorted sets. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the lowest score. + /// @note If all lists are empty and timeout reaches, `bzpopmin` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmin` + /// @see https://redis.io/commands/bzpopmin + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + /// @brief Pop the member with lowest score from multiple sorted set in a blocking way. + /// @param il Initializer list of sorted sets. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return Key-member-score tuple with the lowest score. + /// @note If all lists are empty and timeout reaches, `bzpopmin` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::zpopmin` + /// @see https://redis.io/commands/bzpopmin + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + /// @brief Add or update a member with score to sorted set. + /// @param key Key where the sorted set is stored. + /// @param member Member to be added. + /// @param score Score of the member. + /// @param type Options for zadd command: + /// - UpdateType::EXIST: Add the member only if it already exists. + /// - UpdateType::NOT_EXIST: Add the member only if it does not exist. + /// - UpdateType::ALWAYS: Always add the member no matter whether it exists. + /// @param changed Whether change the return value from number of newly added member to + /// number of members changed (i.e. added and updated). + /// @return Number of added members or number of added and updated members depends on `changed`. + /// @note We don't support the INCR option, because in this case, the return value of zadd + /// command is NOT of type long long. However, you can always use the generic interface + /// to send zadd command with INCR option: + /// `auto score = redis.command("ZADD", "key", "XX", "INCR", 10, "mem");` + /// @see `UpdateType` + /// @see https://redis.io/commands/zadd + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + /// @brief Add or update multiple members with score to sorted set. + /// + /// Example: + /// @code{.cpp} + /// std::unordered_map m = {{"m1", 1.2}, {"m2", 2.3}}; + /// redis.zadd("zset", m.begin(), m.end()); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param first Iterator to the first member-score pair. + /// @param last Off-the-end iterator to the member-score pairs range. + /// @param type Options for zadd command: + /// - UpdateType::EXIST: Add the member only if it already exists. + /// - UpdateType::NOT_EXIST: Add the member only if it does not exist. + /// - UpdateType::ALWAYS: Always add the member no matter whether it exists. + /// @param changed Whether change the return value from number of newly added member to + /// number of members changed (i.e. added and updated). + /// @return Number of added members or number of added and updated members depends on `changed`. + /// @note We don't support the INCR option, because in this case, the return value of zadd + /// command is NOT of type long long. However, you can always use the generic interface + /// to send zadd command with INCR option: + /// `auto score = redis.command("ZADD", "key", "XX", "INCR", 10, "mem");` + /// @see `UpdateType` + /// @see https://redis.io/commands/zadd + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + /// @brief Add or update multiple members with score to sorted set. + /// + /// Example: + /// @code{.cpp} + /// redis.zadd("zset", {std::make_pair("m1", 1.4), std::make_pair("m2", 2.3)}); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param first Iterator to the first member-score pair. + /// @param last Off-the-end iterator to the member-score pairs range. + /// @param type Options for zadd command: + /// - UpdateType::EXIST: Add the member only if it already exists. + /// - UpdateType::NOT_EXIST: Add the member only if it does not exist. + /// - UpdateType::ALWAYS: Always add the member no matter whether it exists. + /// @param changed Whether change the return value from number of newly added member to + /// number of members changed (i.e. added and updated). + /// @return Number of added members or number of added and updated members depends on `changed`. + /// @note We don't support the INCR option, because in this case, the return value of zadd + /// command is NOT of type long long. However, you can always use the generic interface + /// to send zadd command with INCR option: + /// `auto score = redis.command("ZADD", "key", "XX", "INCR", 10, "mem");` + /// @see `UpdateType` + /// @see https://redis.io/commands/zadd + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + /// @brief Get the number of members in the sorted set. + /// @param key Key where the sorted set is stored. + /// @return Number of members in the sorted set. + /// @see https://redis.io/commands/zcard + long long zcard(const StringView &key); + + /// @brief Get the number of members with score between a min-max score range. + /// + /// Example: + /// @code{.cpp} + /// // Count members with score between (2.3, 5] + /// redis.zcount("zset", BoundedInterval(2.3, 5, BoundType::LEFT_OPEN)); + /// // Count members with score between [2.3, 5) + /// redis.zcount("zset", BoundedInterval(2.3, 5, BoundType::RIGHT_OPEN)); + /// // Count members with score between (2.3, 5) + /// redis.zcount("zset", BoundedInterval(2.3, 5, BoundType::OPEN)); + /// // Count members with score between [2.3, 5] + /// redis.zcount("zset", BoundedInterval(2.3, 5, BoundType::CLOSED)); + /// // Count members with score between [2.3, +inf) + /// redis.zcount("zset", LeftBoundedInterval(2.3, BoundType::RIGHT_OPEN)); + /// // Count members with score between (2.3, +inf) + /// redis.zcount("zset", LeftBoundedInterval(2.3, BoundType::OPEN)); + /// // Count members with score between (-inf, 5] + /// redis.zcount("zset", RightBoundedInterval(5, BoundType::LEFT_OPEN)); + /// // Count members with score between (-inf, 5) + /// redis.zcount("zset", RightBoundedInterval(5, BoundType::OPEN)); + /// // Count members with score between (-inf, +inf) + /// redis.zcount("zset", UnboundedInterval{}); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval The min-max score range. + /// @return Number of members with score between a min-max score range. + /// @see `BoundedInterval` + /// @see `LeftBoundedInterval` + /// @see `RightBoundedInterval` + /// @see `UnboundedInterval` + /// @see `BoundType` + /// @see https://redis.io/commands/zcount + // TODO: add a string version of Interval: zcount("key", "(2.3", "5"). + template + long long zcount(const StringView &key, const Interval &interval); + + /// @brief Increment the score of given member. + /// @param key Key where the sorted set is stored. + /// @param increment Increment. + /// @param member Member. + /// @return The score of the member after the operation. + /// @see https://redis.io/commands/zincrby + double zincrby(const StringView &key, double increment, const StringView &member); + + /// @brief Copy a sorted set to another one with the scores being multiplied by a factor. + /// @param destination Key of the destination sorted set. + /// @param key Key of the source sorted set. + /// @param weight Weight to be multiplied to the score of each member. + /// @return The number of members in the sorted set. + /// @note There's no aggregation type parameter for single key overload, since these 3 types + /// have the same effect. + /// @see `Redis::zunionstore` + /// @see https://redis.io/commands/zinterstore + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + /// @brief Get intersection of multiple sorted sets, and store the result to another one. + /// + /// Example: + /// @code{.cpp} + /// // Use the default weight, i.e. 1, + /// // and use the sum of the all scores as the score of the result: + /// std::vector keys = {"k1", "k2", "k3"}; + /// redis.zinterstore("destination", keys.begin(), keys.end()); + /// // Each sorted set has a different weight, + /// // and the score of the result is the min of all scores. + /// std::vector> keys_with_weights = {{"k1", 1}, {"k2", 2}}; + /// redis.zinterstore("destination", keys_with_weights.begin(), + /// keys_with_weights.end(), Aggregation::MIN); + /// // NOTE: `keys_with_weights` can also be of type `std::unordered_map`. + /// // However, it will be slower than std::vector>, since we use + /// // `std::distance(first, last)` to calculate the *numkeys* parameter. + /// @endcode + /// @param destination Key of the destination sorted set. + /// @param first Iterator to the first sorted set (might with weight). + /// @param last Off-the-end iterator to the sorted set range. + /// @param type How the scores are aggregated. + /// - Aggregation::SUM: Score of a member is the sum of all scores. + /// - Aggregation::MIN: Score of a member is the min of all scores. + /// - Aggregation::MAX: Score of a member is the max of all scores. + /// @return The number of members in the resulting sorted set. + /// @note The score of each member can be multiplied by a factor, i.e. weight. If `Input` is an + /// iterator to a container of `std::string`, we use the default weight, i.e. 1, and send + /// *ZINTERSTORE dest numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + /// If `Input` is an iterator to a container of `std::pair`, + /// i.e. key-weight pair, we send the command with the given weights: + /// *ZINTERSTORE dest numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]*. + /// See the *Example* part for examples on how to use this command. + /// @see `Redis::zunionstore` + /// @see https://redis.io/commands/zinterstore + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + /// @brief Get intersection of multiple sorted sets, and store the result to another one. + /// + /// Example: + /// @code{.cpp} + /// // Use the default weight, i.e. 1, + /// // and use the sum of the all scores as the score of the result: + /// redis.zinterstore("destination", {"k1", "k2"}); + /// // Each sorted set has a different weight, + /// // and the score of the result is the min of all scores. + /// redis.zinterstore("destination", + /// {std::make_pair("k1", 1), std::make_pair("k2", 2)}, Aggregation::MIN); + /// @endcode + /// @param destination Key of the destination sorted set. + /// @param il Initializer list of sorted set. + /// @param type How the scores are aggregated. + /// - Aggregation::SUM: Score of a member is the sum of all scores. + /// - Aggregation::MIN: Score of a member is the min of all scores. + /// - Aggregation::MAX: Score of a member is the max of all scores. + /// @return The number of members in the resulting sorted set. + /// @note The score of each member can be multiplied by a factor, i.e. weight. If `T` is + /// of type `std::string`, we use the default weight, i.e. 1, and send + /// *ZINTERSTORE dest numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + /// If `T` is of type `std::pair`, i.e. key-weight pair, + /// we send the command with the given weights: + /// *ZINTERSTORE dest numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]*. + /// See the *Example* part for examples on how to use this command. + /// @see `Redis::zunionstore` + /// @see https://redis.io/commands/zinterstore + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + /// @brief Get the number of members between a min-max range in lexicographical order. + /// + /// Example: + /// @code{.cpp} + /// // Count members between (abc, abd] + /// redis.zlexcount("zset", BoundedInterval("abc", "abd", BoundType::LEFT_OPEN)); + /// // Count members between [abc, abd) + /// redis.zlexcount("zset", BoundedInterval("abc", "abd", BoundType::RIGHT_OPEN)); + /// // Count members between (abc, abd) + /// redis.zlexcount("zset", BoundedInterval("abc", "abd", BoundType::OPEN)); + /// // Count members between [abc, abd] + /// redis.zlexcount("zset", BoundedInterval("abc", "abd", BoundType::CLOSED)); + /// // Count members between [abc, +inf) + /// redis.zlexcount("zset", LeftBoundedInterval("abc", BoundType::RIGHT_OPEN)); + /// // Count members between (abc, +inf) + /// redis.zlexcount("zset", LeftBoundedInterval("abc", BoundType::OPEN)); + /// // Count members between (-inf, "abd"] + /// redis.zlexcount("zset", RightBoundedInterval("abd", BoundType::LEFT_OPEN)); + /// // Count members between (-inf, "abd") + /// redis.zlexcount("zset", RightBoundedInterval("abd", BoundType::OPEN)); + /// // Count members between (-inf, +inf) + /// redis.zlexcount("zset", UnboundedInterval{}); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval The min-max range in lexicographical order. + /// @return Number of members between a min-max range in lexicographical order. + /// @see `BoundedInterval` + /// @see `LeftBoundedInterval` + /// @see `RightBoundedInterval` + /// @see `UnboundedInterval` + /// @see `BoundType` + /// @see https://redis.io/commands/zlexcount + // TODO: add a string version of Interval: zlexcount("key", "(abc", "abd"). + template + long long zlexcount(const StringView &key, const Interval &interval); + + /// @brief Pop the member with highest score from sorted set. + /// @param key Key where the sorted set is stored. + /// @return Member-score pair with the highest score. + /// @note If sorted set is empty `zpopmax` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::bzpopmax` + /// @see https://redis.io/commands/zpopmax + Optional> zpopmax(const StringView &key); + + /// @brief Pop multiple members with highest score from sorted set. + /// @param key Key where the sorted set is stored. + /// @param count Number of members to be popped. + /// @param output Output iterator to the destination where the result is saved. + /// @note The number of returned members might be less than `count`. + /// @see `Redis::bzpopmax` + /// @see https://redis.io/commands/zpopmax + template + void zpopmax(const StringView &key, long long count, Output output); + + /// @brief Pop the member with lowest score from sorted set. + /// @param key Key where the sorted set is stored. + /// @return Member-score pair with the lowest score. + /// @note If sorted set is empty `zpopmin` returns + /// `Optional>{}` (`std::nullopt`). + /// @see `Redis::bzpopmin` + /// @see https://redis.io/commands/zpopmin + Optional> zpopmin(const StringView &key); + + /// @brief Pop multiple members with lowest score from sorted set. + /// @param key Key where the sorted set is stored. + /// @param count Number of members to be popped. + /// @param output Output iterator to the destination where the result is saved. + /// @note The number of returned members might be less than `count`. + /// @see `Redis::bzpopmin` + /// @see https://redis.io/commands/zpopmin + template + void zpopmin(const StringView &key, long long count, Output output); + + /// @brief Get a range of members by rank (ordered from lowest to highest). + /// + /// Example: + /// @code{.cpp} + /// // send *ZRANGE* command without the *WITHSCORES* option: + /// std::vector result; + /// redis.zrange("zset", 0, -1, std::back_inserter(result)); + /// // send command with *WITHSCORES* option: + /// std::vector> with_score; + /// redis.zrange("zset", 0, -1, std::back_inserter(with_score)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param start Start rank. Inclusive and can be negative. + /// @param stop Stop rank. Inclusive and can be negative. + /// @param output Output iterator to the destination where the result is saved. + /// @note This method can also return the score of each member. If `output` is an iterator + /// to a container of `std::string`, we send *ZRANGE key start stop* command. + /// If it's an iterator to a container of `std::pair`, + /// we send *ZRANGE key start stop WITHSCORES* command. See the *Example* part on + /// how to use this method. + /// @see `Redis::zrevrange` + /// @see https://redis.io/commands/zrange + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + /// @brief Get a range of members by lexicographical order (from lowest to highest). + /// + /// Example: + /// @code{.cpp} + /// std::vector result; + /// // Get members between [abc, abd]. + /// redis.zrangebylex("zset", BoundedInterval("abc", "abd", BoundType::CLOSED), + /// std::back_inserter(result)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by lexicographical order. + /// @param output Output iterator to the destination where the result is saved. + /// @note See `Redis::zlexcount`'s *Example* part for how to set `interval` parameter. + /// @see `Redis::zlexcount` + /// @see `BoundedInterval` + /// @see `LeftBoundedInterval` + /// @see `RightBoundedInterval` + /// @see `UnboundedInterval` + /// @see `BoundType` + /// @see `Redis::zrevrangebylex` + /// @see https://redis.io/commands/zrangebylex + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + /// @brief Get a range of members by lexicographical order (from lowest to highest). + /// + /// Example: + /// @code{.cpp} + /// std::vector result; + /// // Limit the result to at most 5 members starting from 10. + /// LimitOptions opts; + /// opts.offset = 10; + /// opts.count = 5; + /// // Get members between [abc, abd]. + /// redis.zrangebylex("zset", BoundedInterval("abc", "abd", BoundType::CLOSED), + /// opts, std::back_inserter(result)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by lexicographical order. + /// @param opts Options to do pagination, i.e. *LIMIT offset count*. + /// @param output Output iterator to the destination where the result is saved. + /// @note See `Redis::zlexcount`'s *Example* part for how to set `interval` parameter. + /// @see `Redis::zlexcount` + /// @see `BoundedInterval` + /// @see `LeftBoundedInterval` + /// @see `RightBoundedInterval` + /// @see `UnboundedInterval` + /// @see `BoundType` + /// @see `LimitOptions` + /// @see `Redis::zrevrangebylex` + /// @see https://redis.io/commands/zrangebylex + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + /// @brief Get a range of members by score (ordered from lowest to highest). + /// + /// Example: + /// @code{.cpp} + /// // Send *ZRANGEBYSCORE* command without the *WITHSCORES* option: + /// std::vector result; + /// // Get members whose score between (3, 6). + /// redis.zrangebyscore("zset", BoundedInterval(3, 6, BoundType::OPEN), + /// std::back_inserter(result)); + /// // Send command with *WITHSCORES* option: + /// std::vector> with_score; + /// // Get members whose score between [3, +inf). + /// redis.zrangebyscore("zset", LeftBoundedInterval(3, BoundType::RIGHT_OPEN), + /// std::back_inserter(with_score)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by score. + /// @param output Output iterator to the destination where the result is saved. + /// @note This method can also return the score of each member. If `output` is an iterator + /// to a container of `std::string`, we send *ZRANGEBYSCORE key min max* command. + /// If it's an iterator to a container of `std::pair`, + /// we send *ZRANGEBYSCORE key min max WITHSCORES* command. See the *Example* part on + /// how to use this method. + /// @note See `Redis::zcount`'s *Example* part for how to set the `interval` parameter. + /// @see `Redis::zrevrangebyscore` + /// @see https://redis.io/commands/zrangebyscore + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + /// @brief Get a range of members by score (ordered from lowest to highest). + /// + /// Example: + /// @code{.cpp} + /// // Send *ZRANGEBYSCORE* command without the *WITHSCORES* option: + /// std::vector result; + /// // Only return at most 5 members starting from 10. + /// LimitOptions opts; + /// opts.offset = 10; + /// opts.count = 5; + /// // Get members whose score between (3, 6). + /// redis.zrangebyscore("zset", BoundedInterval(3, 6, BoundType::OPEN), + /// opts, std::back_inserter(result)); + /// // Send command with *WITHSCORES* option: + /// std::vector> with_score; + /// // Get members whose score between [3, +inf). + /// redis.zrangebyscore("zset", LeftBoundedInterval(3, BoundType::RIGHT_OPEN), + /// opts, std::back_inserter(with_score)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by score. + /// @param opts Options to do pagination, i.e. *LIMIT offset count*. + /// @param output Output iterator to the destination where the result is saved. + /// @note This method can also return the score of each member. If `output` is an iterator + /// to a container of `std::string`, we send *ZRANGEBYSCORE key min max* command. + /// If it's an iterator to a container of `std::pair`, + /// we send *ZRANGEBYSCORE key min max WITHSCORES* command. See the *Example* part on + /// how to use this method. + /// @note See `Redis::zcount`'s *Example* part for how to set the `interval` parameter. + /// @see `Redis::zrevrangebyscore` + /// @see https://redis.io/commands/zrangebyscore + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + /// @brief Get the rank (from low to high) of the given member in the sorted set. + /// @param key Key where the sorted set is stored. + /// @param member Member. + /// @return The rank of the given member. + /// @note If the member does not exist, `zrank` returns `OptionalLongLong{}` (`std::nullopt`). + /// @see https://redis.io/commands/zrank + OptionalLongLong zrank(const StringView &key, const StringView &member); + + /// @brief Remove the given member from sorted set. + /// @param key Key where the sorted set is stored. + /// @param member Member to be removed. + /// @return Whether the member has been removed. + /// @retval 1 If the member exists, and has been removed. + /// @retval 0 If the member does not exist. + /// @see https://redis.io/commands/zrem + long long zrem(const StringView &key, const StringView &member); + + /// @brief Remove multiple members from sorted set. + /// @param key Key where the sorted set is stored. + /// @param first Iterator to the first member. + /// @param last Off-the-end iterator to the given range. + /// @return Number of members that have been removed. + /// @see https://redis.io/commands/zrem + template + long long zrem(const StringView &key, Input first, Input last); + + /// @brief Remove multiple members from sorted set. + /// @param key Key where the sorted set is stored. + /// @param il Initializer list of members to be removed. + /// @return Number of members that have been removed. + /// @see https://redis.io/commands/zrem + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + /// @brief Remove members in the given range of lexicographical order. + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by lexicographical order. + /// @note See `Redis::zlexcount`'s *Example* part for how to set `interval` parameter. + /// @return Number of members removed. + /// @see `Redis::zlexcount` + /// @see `BoundedInterval` + /// @see `LeftBoundedInterval` + /// @see `RightBoundedInterval` + /// @see `UnboundedInterval` + /// @see `BoundType` + /// @see https://redis.io/commands/zremrangebylex + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + /// @brief Remove members in the given range ordered by rank. + /// @param key Key where the sorted set is stored. + /// @param start Start rank. + /// @param stop Stop rank. + /// @return Number of members removed. + /// @see https://redis.io/commands/zremrangebyrank + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + /// @brief Remove members in the given range ordered by score. + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by score. + /// @return Number of members removed. + /// @note See `Redis::zcount`'s *Example* part for how to set the `interval` parameter. + /// @see https://redis.io/commands/zremrangebyscore + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + /// @brief Get a range of members by rank (ordered from highest to lowest). + /// + /// Example: + /// @code{.cpp} + /// // send *ZREVRANGE* command without the *WITHSCORES* option: + /// std::vector result; + /// redis.zrevrange("key", 0, -1, std::back_inserter(result)); + /// // send command with *WITHSCORES* option: + /// std::vector> with_score; + /// redis.zrevrange("key", 0, -1, std::back_inserter(with_score)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param start Start rank. Inclusive and can be negative. + /// @param stop Stop rank. Inclusive and can be negative. + /// @param output Output iterator to the destination where the result is saved. + /// @note This method can also return the score of each member. If `output` is an iterator + /// to a container of `std::string`, we send *ZREVRANGE key start stop* command. + /// If it's an iterator to a container of `std::pair`, + /// we send *ZREVRANGE key start stop WITHSCORES* command. See the *Example* part on + /// how to use this method. + /// @see `Redis::zrange` + /// @see https://redis.io/commands/zrevrange + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + /// @brief Get a range of members by lexicographical order (from highest to lowest). + /// + /// Example: + /// @code{.cpp} + /// std::vector result; + /// // Get members between [abc, abd] in reverse order. + /// redis.zrevrangebylex("zset", BoundedInterval("abc", "abd", BoundType::CLOSED), + /// std::back_inserter(result)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by lexicographical order. + /// @param output Output iterator to the destination where the result is saved. + /// @note See `Redis::zlexcount`'s *Example* part for how to set `interval` parameter. + /// @see `Redis::zlexcount` + /// @see `BoundedInterval` + /// @see `LeftBoundedInterval` + /// @see `RightBoundedInterval` + /// @see `UnboundedInterval` + /// @see `BoundType` + /// @see `Redis::zrangebylex` + /// @see https://redis.io/commands/zrevrangebylex + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + /// @brief Get a range of members by lexicographical order (from highest to lowest). + /// + /// Example: + /// @code{.cpp} + /// std::vector result; + /// // Limit the result to at most 5 members starting from 10. + /// LimitOptions opts; + /// opts.offset = 10; + /// opts.count = 5; + /// // Get members between [abc, abd] in reverse order. + /// redis.zrevrangebylex("zset", BoundedInterval("abc", "abd", BoundType::CLOSED), + /// opts, std::back_inserter(result)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by lexicographical order. + /// @param opts Options to do pagination, i.e. *LIMIT offset count*. + /// @param output Output iterator to the destination where the result is saved. + /// @note See `Redis::zlexcount`'s *Example* part for how to set `interval` parameter. + /// @see `Redis::zlexcount` + /// @see `BoundedInterval` + /// @see `LeftBoundedInterval` + /// @see `RightBoundedInterval` + /// @see `UnboundedInterval` + /// @see `BoundType` + /// @see `LimitOptions` + /// @see `Redis::zrangebylex` + /// @see https://redis.io/commands/zrevrangebylex + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + /// @brief Get a range of members by score (ordered from highest to lowest). + /// + /// Example: + /// @code{.cpp} + /// // Send *ZREVRANGEBYSCORE* command without the *WITHSCORES* option: + /// std::vector result; + /// // Get members whose score between (3, 6) in reverse order. + /// redis.zrevrangebyscore("zset", BoundedInterval(3, 6, BoundType::OPEN), + /// std::back_inserter(result)); + /// // Send command with *WITHSCORES* option: + /// std::vector> with_score; + /// // Get members whose score between [3, +inf) in reverse order. + /// redis.zrevrangebyscore("zset", LeftBoundedInterval(3, BoundType::RIGHT_OPEN), + /// std::back_inserter(with_score)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by score. + /// @param output Output iterator to the destination where the result is saved. + /// @note This method can also return the score of each member. If `output` is an iterator + /// to a container of `std::string`, we send *ZREVRANGEBYSCORE key min max* command. + /// If it's an iterator to a container of `std::pair`, + /// we send *ZREVRANGEBYSCORE key min max WITHSCORES* command. See the *Example* part on + /// how to use this method. + /// @note See `Redis::zcount`'s *Example* part for how to set the `interval` parameter. + /// @see `Redis::zrangebyscore` + /// @see https://redis.io/commands/zrevrangebyscore + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + /// @brief Get a range of members by score (ordered from highest to lowest). + /// + /// Example: + /// @code{.cpp} + /// // Send *ZREVRANGEBYSCORE* command without the *WITHSCORES* option: + /// std::vector result; + /// // Only return at most 5 members starting from 10. + /// LimitOptions opts; + /// opts.offset = 10; + /// opts.count = 5; + /// // Get members whose score between (3, 6) in reverse order. + /// redis.zrevrangebyscore("zset", BoundedInterval(3, 6, BoundType::OPEN), + /// opts, std::back_inserter(result)); + /// // Send command with *WITHSCORES* option: + /// std::vector> with_score; + /// // Get members whose score between [3, +inf) in reverse order. + /// redis.zrevrangebyscore("zset", LeftBoundedInterval(3, BoundType::RIGHT_OPEN), + /// opts, std::back_inserter(with_score)); + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param interval the min-max range by score. + /// @param opts Options to do pagination, i.e. *LIMIT offset count*. + /// @param output Output iterator to the destination where the result is saved. + /// @note This method can also return the score of each member. If `output` is an iterator + /// to a container of `std::string`, we send *ZREVRANGEBYSCORE key min max* command. + /// If it's an iterator to a container of `std::pair`, + /// we send *ZREVRANGEBYSCORE key min max WITHSCORES* command. See the *Example* part on + /// how to use this method. + /// @note See `Redis::zcount`'s *Example* part for how to set the `interval` parameter. + /// @see `Redis::zrangebyscore` + /// @see https://redis.io/commands/zrevrangebyscore + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + /// @brief Get the rank (from high to low) of the given member in the sorted set. + /// @param key Key where the sorted set is stored. + /// @param member Member. + /// @return The rank of the given member. + /// @note If the member does not exist, `zrevrank` returns `OptionalLongLong{}` (`std::nullopt`). + /// @see https://redis.io/commands/zrevrank + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + /// @brief Scan members of the given sorted set matching the given pattern. + /// + /// Example: + /// @code{.cpp} + /// auto cursor = 0LL; + /// std::vector> members; + /// while (true) { + /// cursor = redis.zscan("zset", cursor, "pattern:*", + /// 10, std::back_inserter(members)); + /// if (cursor == 0) { + /// break; + /// } + /// } + /// @endcode + /// @param key Key where the sorted set is stored. + /// @param cursor Cursor. + /// @param pattern Pattern of members to be scanned. + /// @param count A hint for how many members to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/zscan + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + /// @brief Scan members of the given sorted set matching the given pattern. + /// @param key Key where the sorted set is stored. + /// @param cursor Cursor. + /// @param pattern Pattern of members to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/zscan + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + /// @brief Scan all members of the given sorted set. + /// @param key Key where the sorted set is stored. + /// @param cursor Cursor. + /// @param count A hint for how many members to be scanned. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/zscan + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + /// @brief Scan all members of the given sorted set. + /// @param key Key where the sorted set is stored. + /// @param cursor Cursor. + /// @param output Output iterator to the destination where the result is saved. + /// @return The cursor to be used for the next scan operation. + /// @see https://redis.io/commands/zscan + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + /// @brief Get the score of the given member. + /// @param key Key where the sorted set is stored. + /// @param member Member. + /// @return The score of the member. + /// @note If member does not exist, `zscore` returns `OptionalDouble{}` (`std::nullopt`). + /// @see https://redis.io/commands/zscore + OptionalDouble zscore(const StringView &key, const StringView &member); + + /// @brief Copy a sorted set to another one with the scores being multiplied by a factor. + /// @param destination Key of the destination sorted set. + /// @param key Key of the source sorted set. + /// @param weight Weight to be multiplied to the score of each member. + /// @return The number of members in the sorted set. + /// @note There's no aggregation type parameter for single key overload, since these 3 types + /// have the same effect. + /// @see `Redis::zinterstore` + /// @see https://redis.io/commands/zinterstore + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + /// @brief Get union of multiple sorted sets, and store the result to another one. + /// + /// Example: + /// @code{.cpp} + /// // Use the default weight, i.e. 1, + /// // and use the sum of the all scores as the score of the result: + /// std::vector keys = {"k1", "k2", "k3"}; + /// redis.zunionstore("destination", keys.begin(), keys.end()); + /// // Each sorted set has a different weight, + /// // and the score of the result is the min of all scores. + /// std::vector> keys_with_weights = {{"k1", 1}, {"k2", 2}}; + /// redis.zunionstore("destination", keys_with_weights.begin(), + /// keys_with_weights.end(), Aggregation::MIN); + /// // NOTE: `keys_with_weights` can also be of type `std::unordered_map`. + /// // However, it will be slower than std::vector>, since we use + /// // `std::distance(first, last)` to calculate the *numkeys* parameter. + /// @endcode + /// @param destination Key of the destination sorted set. + /// @param first Iterator to the first sorted set (might with weight). + /// @param last Off-the-end iterator to the sorted set range. + /// @param type How the scores are aggregated. + /// - Aggregation::SUM: Score of a member is the sum of all scores. + /// - Aggregation::MIN: Score of a member is the min of all scores. + /// - Aggregation::MAX: Score of a member is the max of all scores. + /// @return The number of members in the resulting sorted set. + /// @note The score of each member can be multiplied by a factor, i.e. weight. If `Input` is an + /// iterator to a container of `std::string`, we use the default weight, i.e. 1, and send + /// *ZUNIONSTORE dest numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + /// If `Input` is an iterator to a container of `std::pair`, + /// i.e. key-weight pair, we send the command with the given weights: + /// *ZUNIONSTORE dest numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]*. + /// See the *Example* part for examples on how to use this command. + /// @see `Redis::zinterstore` + /// @see https://redis.io/commands/zunionstore + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + /// @brief Get union of multiple sorted sets, and store the result to another one. + /// + /// Example: + /// @code{.cpp} + /// // Use the default weight, i.e. 1, + /// // and use the sum of the all scores as the score of the result: + /// redis.zunionstore("destination", {"k1", "k2"}); + /// // Each sorted set has a different weight, + /// // and the score of the result is the min of all scores. + /// redis.zunionstore("destination", + /// {std::make_pair("k1", 1), std::make_pair("k2", 2)}, Aggregation::MIN); + /// @endcode + /// @param destination Key of the destination sorted set. + /// @param il Initializer list of sorted set. + /// @param type How the scores are aggregated. + /// - Aggregation::SUM: Score of a member is the sum of all scores. + /// - Aggregation::MIN: Score of a member is the min of all scores. + /// - Aggregation::MAX: Score of a member is the max of all scores. + /// @return The number of members in the resulting sorted set. + /// @note The score of each member can be multiplied by a factor, i.e. weight. If `T` is + /// of type `std::string`, we use the default weight, i.e. 1, and send + /// *ZUNIONSTORE dest numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + /// If `T` is of type `std::pair`, i.e. key-weight pair, + /// we send the command with the given weights: + /// *ZUNIONSTORE dest numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]*. + /// See the *Example* part for examples on how to use this command. + /// @see `Redis::zinterstore` + /// @see https://redis.io/commands/zunionstore + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + /// @brief Add the given element to a hyperloglog. + /// @param key Key of the hyperloglog. + /// @param element Element to be added. + /// @return Whether any of hyperloglog's internal register has been altered. + /// @retval true If at least one internal register has been altered. + /// @retval false If none of internal registers has been altered. + /// @note When `pfadd` returns false, it does not mean that this method failed to add + /// an element to the hyperloglog. Instead it means that the internal registers + /// were not altered. If `pfadd` fails, it will throw an exception of `Exception` type. + /// @see https://redis.io/commands/pfadd + bool pfadd(const StringView &key, const StringView &element); + + /// @brief Add the given elements to a hyperloglog. + /// @param key Key of the hyperloglog. + /// @param first Iterator to the first element. + /// @param last Off-the-end iterator to the given range. + /// @return Whether any of hyperloglog's internal register has been altered. + /// @retval true If at least one internal register has been altered. + /// @retval false If none of internal registers has been altered. + /// @note When `pfadd` returns false, it does not mean that this method failed to add + /// an element to the hyperloglog. Instead it means that the internal registers + /// were not altered. If `pfadd` fails, it will throw an exception of `Exception` type. + /// @see https://redis.io/commands/pfadd + template + bool pfadd(const StringView &key, Input first, Input last); + + /// @brief Add the given elements to a hyperloglog. + /// @param key Key of the hyperloglog. + /// @param il Initializer list of elements to be added. + /// @return Whether any of hyperloglog's internal register has been altered. + /// @retval true If at least one internal register has been altered. + /// @retval false If none of internal registers has been altered. + /// @note When `pfadd` returns false, it does not mean that this method failed to add + /// an element to the hyperloglog. Instead it means that the internal registers + /// were not altered. If `pfadd` fails, it will throw an exception of `Exception` type. + /// @see https://redis.io/commands/pfadd + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + OptionalString geohash(const StringView &key, const StringView &member); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + Optional> geopos(const StringView &key, const StringView &member); + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + + /// @brief Get members in geo range, i.e. a circle, and store them in a sorted set. + /// @param key Key of the GEO set. + /// @param loc Location encoded with pair. + /// @param radius Radius of the range. + /// @param unit Radius unit. + /// @param destination Key of the destination sorted set. + /// @param store_dist Whether store distance info instead of geo info to destination. + /// @param count Limit the first N members. + /// @return Number of members stored in destination. + /// @note Before Redis 6.2.6, if key does not exist, returns `OptionalLongLong{}` (`std::nullopt`). + /// Since Redis 6.2.6, if key does not exist, returns 0. + /// @see `GeoUnit` + /// @see `Redis::georadiusbymember` + /// @see https://redis.io/commands/georadius + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + /// @brief Get members in geo range, i.e. a circle, and store them in a sorted set. + /// @param key Key of the GEO set. + /// @param member Member which is the center of the circle. + /// @param radius Radius of the range. + /// @param unit Radius unit. + /// @param destination Key of the destination sorted set. + /// @param store_dist Whether store distance info instead of geo info to destination. + /// @param count Limit the first N members. + /// @return Number of members stored in destination. + /// @note Before Redis 6.2.6, if key does not exist, returns `OptionalLongLong{}` (`std::nullopt`). + /// Since Redis 6.2.6, if key does not exist, returns 0. + /// @note If member does not exist, throw an `ReplyError`. + /// @see `GeoUnit` + /// @see `Redis::georadius` + /// @see https://redis.io/commands/georadiusbymember + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last); + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last, + Output output); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last, + Output output); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + /// @brief Check if the given script exists. + /// @param sha1 SHA1 digest of the script. + /// @return Whether the script exists. + /// @retval true If the script exists. + /// @retval false If the script does not exist. + /// @see https://redis.io/commands/script-exists + bool script_exists(const StringView &sha1); + + template + void script_exists(Input first, Input last, Output output); + + template + void script_exists(std::initializer_list il, Output output) { + script_exists(il.begin(), il.end(), output); + } + + void script_flush(); + + void script_kill(); + + std::string script_load(const StringView &script); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Transaction commands. + void watch(const StringView &key); + + template + void watch(Input first, Input last); + + template + void watch(std::initializer_list il) { + watch(il.begin(), il.end()); + } + + void unwatch(); + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first ,last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + template + friend class QueuedRedis; + + friend class RedisCluster; + + // For internal use. + explicit Redis(const GuardedConnectionSPtr &connection); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + // Pool Mode. + // Public constructors create a *Redis* instance with a pool. + // In this case, *_connection* is a null pointer, and is never used. + ConnectionPoolSPtr _pool; + + // Single Connection Mode. + // Private constructor creates a *Redis* instance with a single connection. + // This is used when we create Transaction, Pipeline and Subscriber. + // In this case, *_pool* is empty, and is never used. + GuardedConnectionSPtr _connection; +}; + +} + +} + +#include "redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis.hpp b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis.hpp new file mode 100644 index 000000000..c560ce111 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis.hpp @@ -0,0 +1,1342 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_HPP + +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +template +auto Redis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (_connection) { + // Single Connection Mode. + // TODO: In this case, should we reconnect? + auto &connection = _connection->connection(); + if (connection.broken()) { + throw Error("Connection is broken"); + } + + return _command(connection, cmd, std::forward(args)...); + } else { + assert(_pool); + + // Pool Mode, i.e. get connection from pool. + SafeConnection connection(*_pool); + + return _command(connection.connection(), cmd, std::forward(args)...); + } +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, ReplyUPtr>::type { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + range_check("command", first, last); + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +Result Redis::command(const StringView &cmd_name, Args &&...args) { + auto r = command(cmd_name, std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long Redis::del(Input first, Input last) { + range_check("DEL", first, last); + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::exists(Input first, Input last) { + range_check("EXISTS", first, last); + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool Redis::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool Redis::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +template +void Redis::keys(const StringView &pattern, Output output) { + auto reply = command(cmd::keys, pattern); + + reply::to_array(*reply, output); +} + +inline bool Redis::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool Redis::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void Redis::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long Redis::scan(long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::scan, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::scan(long long cursor, + const StringView &pattern, + Output output) { + return scan(cursor, pattern, 10, output); +} + +template +inline long long Redis::scan(long long cursor, + long long count, + Output output) { + return scan(cursor, "*", count, output); +} + +template +inline long long Redis::scan(long long cursor, + Output output) { + return scan(cursor, "*", 10, output); +} + +template +long long Redis::touch(Input first, Input last) { + range_check("TOUCH", first, last); + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::unlink(Input first, Input last) { + range_check("UNLINK", first, last); + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); +} + +// STRING commands. + +template +long long Redis::bitop(BitOp op, const StringView &destination, Input first, Input last) { + range_check("BITOP", first, last); + + auto reply = command(cmd::bitop_range, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::mget(Input first, Input last, Output output) { + range_check("MGET", first, last); + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::mset(Input first, Input last) { + range_check("MSET", first, last); + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool Redis::msetnx(Input first, Input last) { + range_check("MSETNX", first, last); + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void Redis::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void Redis::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair Redis::blpop(Input first, Input last, long long timeout) { + range_check("BLPOP", first, last); + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair Redis::brpop(Input first, Input last, long long timeout) { + range_check("BRPOP", first, last); + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long Redis::lpush(const StringView &key, Input first, Input last) { + range_check("LPUSH", first, last); + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long Redis::rpush(const StringView &key, Input first, Input last) { + range_check("RPUSH", first, last); + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long Redis::hdel(const StringView &key, Input first, Input last) { + range_check("HDEL", first, last); + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmget(const StringView &key, Input first, Input last, Output output) { + range_check("HMGET", first, last); + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmset(const StringView &key, Input first, Input last) { + range_check("HMSET", first, last); + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +auto Redis::hset(const StringView &key, Input first, Input last) + -> typename std::enable_if::value, + long long>::type { + range_check("HSET", first, last); + + auto reply = command(cmd::hset_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long Redis::sadd(const StringView &key, Input first, Input last) { + range_check("SADD", first, last); + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sdiff(Input first, Input last, Output output) { + range_check("SDIFF", first, last); + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sdiffstore(const StringView &destination, + Input first, + Input last) { + range_check("SDIFFSTORE", first, last); + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sinter(Input first, Input last, Output output) { + range_check("SINTER", first, last); + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sinterstore(const StringView &destination, + Input first, + Input last) { + range_check("SINTERSTORE", first, last); + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void Redis::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long Redis::srem(const StringView &key, Input first, Input last) { + range_check("SREM", first, last); + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void Redis::sunion(Input first, Input last, Output output) { + range_check("SUNION", first, last); + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sunionstore(const StringView &destination, Input first, Input last) { + range_check("SUNIONSTORE", first, last); + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto Redis::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto Redis::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long Redis::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + range_check("ZADD", first, last); + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long Redis::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + range_check("ZINTERSTORE", first, last); + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long Redis::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void Redis::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zrem(const StringView &key, Input first, Input last) { + range_check("ZREM", first, last); + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long Redis::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + range_check("ZUNIONSTORE", first, last); + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool Redis::pfadd(const StringView &key, Input first, Input last) { + range_check("PFADD", first, last); + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::pfcount(Input first, Input last) { + range_check("PFCOUNT", first, last); + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void Redis::pfmerge(const StringView &destination, + Input first, + Input last) { + range_check("PFMERGE", first, last); + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long Redis::geoadd(const StringView &key, + Input first, + Input last) { + range_check("GEOADD", first, last); + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::geohash(const StringView &key, Input first, Input last, Output output) { + range_check("GEOHASH", first, last); + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::geopos(const StringView &key, Input first, Input last, Output output) { + range_check("GEOPOS", first, last); + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result Redis::eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + auto reply = command(cmd::eval, script, keys_first, keys_last, args_first, args_last); + + return reply::parse(*reply); +} + +template +Result Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return eval(script, keys.begin(), keys.end(), args.begin(), args.end()); +} + +template +void Redis::eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last, + Output output) { + auto reply = command(cmd::eval, + script, + keys_first, keys_last, + args_first, args_last); + + reply::to_array(*reply, output); +} + +template +void Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + eval(script, keys.begin(), keys.end(), args.begin(), args.end(), output); +} + +template +Result Redis::evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + auto reply = command(cmd::evalsha, script, + keys_first, keys_last, args_first, args_last); + + return reply::parse(*reply); +} + +template +Result Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return evalsha(script, keys.begin(), keys.end(), args.begin(), args.end()); +} + +template +void Redis::evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last, + Output output) { + auto reply = command(cmd::evalsha, + script, + keys_first, keys_last, + args_first, args_last); + + reply::to_array(*reply, output); +} + +template +void Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + evalsha(script, keys.begin(), keys.end(), args.begin(), args.end(), output); +} + +template +void Redis::script_exists(Input first, Input last, Output output) { + range_check("SCRIPT EXISTS", first, last); + + auto reply = command(cmd::script_exists_range, first, last); + + reply::to_array(*reply, output); +} + +// Transaction commands. + +template +void Redis::watch(Input first, Input last) { + auto reply = command(cmd::watch_range, first, last); + + reply::parse(*reply); +} + +// Stream commands. + +template +long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long Redis::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto Redis::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + range_check("XREAD", first, last); + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + range_check("XREAD", first, last); + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + range_check("XREADGROUP", first, last); + + auto reply = command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + range_check("XREADGROUP", first, last); + + auto reply = command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + auto reply = connection.recv(); + + return reply; +} + +template +inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis_cluster.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis_cluster.h new file mode 100644 index 000000000..63c6131c1 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis_cluster.h @@ -0,0 +1,1439 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H + +#include +#include +#include +#include +#include "shards_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "redis.h" +#include "connection.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class RedisCluster { +public: + RedisCluster(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}, + Role role = Role::MASTER) : _pool(pool_opts, connection_opts, role) {} + + // Construct RedisCluster with URI: + // "tcp://127.0.0.1" or "tcp://127.0.0.1:6379" + // Only need to specify one URI. + explicit RedisCluster(const std::string &uri); + + RedisCluster(const RedisCluster &) = delete; + RedisCluster& operator=(const RedisCluster &) = delete; + + RedisCluster(RedisCluster &&) = default; + RedisCluster& operator=(RedisCluster &&) = default; + + Redis redis(const StringView &hash_tag, bool new_connection = true); + + Pipeline pipeline(const StringView &hash_tag, bool new_connection = true); + + Transaction transaction(const StringView &hash_tag, bool piped = false, bool new_connection = true); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + template + auto hset(const StringView &key, Input first, Input last) + -> typename std::enable_if::value, long long>::type; + + template + long long hset(const StringView &key, std::initializer_list il) { + return hset(key, il.begin(), il.end()); + } + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + OptionalString geohash(const StringView &key, const StringView &member); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + Optional> geopos(const StringView &key, const StringView &member); + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last); + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last, + Output output); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last, + Output output); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first, last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class Command { + public: + explicit Command(const StringView &cmd_name) : _cmd_name(cmd_name) {} + + template + void operator()(Connection &connection, Args &&...args) const { + CmdArgs cmd_args; + cmd_args.append(_cmd_name, std::forward(args)...); + connection.send(cmd_args); + } + + private: + StringView _cmd_name; + }; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + ReplyUPtr _command(Cmd cmd, Connection &connection, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::true_type, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::false_type, Input &&first, Args &&...args); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _range_command(Cmd cmd, std::true_type, Input input, Args &&...args); + + template + ReplyUPtr _range_command(Cmd cmd, std::false_type, Input input, Args &&...args); + + void _asking(Connection &connection); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + ShardsPool _pool; +}; + +} + +} + +#include "redis_cluster.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis_cluster.hpp b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis_cluster.hpp new file mode 100644 index 000000000..2153d181b --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/redis_cluster.hpp @@ -0,0 +1,1403 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP + +#include +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" +#include "shards_pool.h" + +namespace sw { + +namespace redis { + +template +auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + return _command(cmd, + std::is_convertible::type, StringView>(), + std::forward(key), + std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type { + auto cmd = Command(cmd_name); + + return _generic_command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type { + auto r = command(cmd_name, std::forward(key), std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(key), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last || std::next(first) == last) { + throw Error("command: invalid range"); + } + + const auto &key = *first; + ++first; + + auto cmd = [&key](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + cmd_args.append(key); + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long RedisCluster::del(Input first, Input last) { + range_check("DEL", first, last); + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::exists(Input first, Input last) { + range_check("EXISTS", first, last); + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool RedisCluster::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool RedisCluster::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void RedisCluster::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long RedisCluster::touch(Input first, Input last) { + range_check("TOUCH", first, last); + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::unlink(Input first, Input last) { + range_check("UNLINK", first, last); + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +// STRING commands. + +template +long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) { + range_check("BITOP", first, last); + + auto reply = _command(cmd::bitop_range, destination, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::mget(Input first, Input last, Output output) { + range_check("MGET", first, last); + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::mset(Input first, Input last) { + range_check("MSET", first, last); + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool RedisCluster::msetnx(Input first, Input last) { + range_check("MSETNX", first, last); + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void RedisCluster::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void RedisCluster::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) { + range_check("BLPOP", first, last); + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) { + range_check("BRPOP", first, last); + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) { + range_check("LPUSH", first, last); + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) { + range_check("RPUSH", first, last); + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) { + range_check("HDEL", first, last); + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) { + range_check("HMGET", first, last); + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmset(const StringView &key, Input first, Input last) { + range_check("HMSET", first, last); + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +auto RedisCluster::hset(const StringView &key, Input first, Input last) + -> typename std::enable_if::value, + long long>::type { + range_check("HSET", first, last); + + auto reply = command(cmd::hset_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long RedisCluster::sadd(const StringView &key, Input first, Input last) { + range_check("SADD", first, last); + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sdiff(Input first, Input last, Output output) { + range_check("SDIFF", first, last); + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sdiffstore(const StringView &destination, + Input first, + Input last) { + range_check("SDIFFSTORE", first, last); + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sinter(Input first, Input last, Output output) { + range_check("SINTER", first, last); + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sinterstore(const StringView &destination, + Input first, + Input last) { + range_check("SINTERSTORE", first, last); + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::srem(const StringView &key, Input first, Input last) { + range_check("SREM", first, last); + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void RedisCluster::sunion(Input first, Input last, Output output) { + range_check("SUNION", first, last); + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) { + range_check("SUNIONSTORE", first, last); + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long RedisCluster::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + range_check("ZADD", first, last); + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + range_check("ZINTERSTORE", first, last); + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zrem(const StringView &key, Input first, Input last) { + range_check("ZREM", first, last); + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long RedisCluster::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + range_check("ZUNIONSTORE", first, last); + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool RedisCluster::pfadd(const StringView &key, Input first, Input last) { + range_check("PFADD", first, last); + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::pfcount(Input first, Input last) { + range_check("PFCOUNT", first, last); + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::pfmerge(const StringView &destination, + Input first, + Input last) { + range_check("PFMERGE", first, last); + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long RedisCluster::geoadd(const StringView &key, + Input first, + Input last) { + range_check("GEOADD", first, last); + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) { + range_check("GEOHASH", first, last); + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) { + range_check("GEOPOS", first, last); + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result RedisCluster::eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + if (keys_first == keys_last) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys_first, script, keys_first, keys_last, args_first, args_last); + + return reply::parse(*reply); +} + +template +Result RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return eval(script, keys.begin(), keys.end(), args.begin(), args.end()); +} + +template +void RedisCluster::eval(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last, + Output output) { + if (keys_first == keys_last) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, + *keys_first, + script, + keys_first, keys_last, + args_first, args_last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + eval(script, keys.begin(), keys.end(), args.begin(), args.end(), output); +} + +template +Result RedisCluster::evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last) { + if (keys_first == keys_last) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::evalsha, *keys_first, script, + keys_first, keys_last, args_first, args_last); + + return reply::parse(*reply); +} + +template +Result RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return evalsha(script, keys.begin(), keys.end(), args.begin(), args.end()); +} + +template +void RedisCluster::evalsha(const StringView &script, + Keys keys_first, + Keys keys_last, + Args args_first, + Args args_last, + Output output) { + if (keys_first == keys_last) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::evalsha, + *keys_first, + script, + keys_first, keys_last, + args_first, args_last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + evalsha(script, keys.begin(), keys.end(), args.begin(), args.end(), output); +} + +// Stream commands. + +template +long long RedisCluster::xack(const StringView &key, + const StringView &group, + Input first, + Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + range_check("XREAD", first, last); + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + range_check("XREAD", first, last); + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + range_check("XREADGROUP", first, last); + + auto reply = _command(cmd::xreadgroup_range, + first->first, + group, + consumer, + first, + last, + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup_block, + key, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + range_check("XREADGROUP", first, last); + + auto reply = _command(cmd::xreadgroup_block_range, + first->first, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type { + return command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type { + auto k = std::to_string(std::forward(key)); + return command(cmd, k, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) { + return _command(cmd, key, key, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) { + return _range_command(cmd, + std::is_convertible< + typename std::decay< + decltype(*std::declval())>::type, StringView>(), + std::forward(first), + std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) { + return _command(cmd, *input, input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) { + return _command(cmd, std::get<0>(*input), input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + return connection.recv(); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) { + for (auto idx = 0; idx < 2; ++idx) { + try { + auto pool = _pool.fetch(key); + assert(pool); + SafeConnection safe_connection(*pool); + + return _command(cmd, safe_connection.connection(), std::forward(args)...); + } catch (const IoError &err) { + // When master is down, one of its replicas will be promoted to be the new master. + // If we try to send command to the old master, we'll get an *IoError*. + // In this case, we need to update the slots mapping. + _pool.update(); + } catch (const ClosedError &err) { + // Node might be removed. + // 1. Get up-to-date slot mapping to check if the node still exists. + _pool.update(); + + // TODO: + // 2. If it's NOT exist, update slot mapping, and retry. + // 3. If it's still exist, that means the node is down, NOT removed, throw exception. + } catch (const MovedError &err) { + // Slot mapping has been changed, update it and try again. + _pool.update(); + } catch (const AskError &err) { + auto pool = _pool.fetch(err.node()); + assert(pool); + SafeConnection safe_connection(*pool); + auto &connection = safe_connection.connection(); + + // 1. send ASKING command. + _asking(connection); + + // 2. resend last command. + try { + return _command(cmd, connection, std::forward(args)...); + } catch (const MovedError &err) { + throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state"); + } + } // For other exceptions, just throw it. + } + + // Possible failures: + // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx', + // while the destination node has NOT run it. + // In this case, client will be redirected by both nodes with MovedError. + // 2. Other failures... + throw Error("Failed to send command with key: " + std::string(key.data(), key.size())); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/reply.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/reply.h new file mode 100644 index 000000000..d89174329 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/reply.h @@ -0,0 +1,435 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H +#define SEWENEW_REDISPLUSPLUS_REPLY_H + +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +struct ReplyDeleter { + void operator()(redisReply *reply) const { + if (reply != nullptr) { + freeReplyObject(reply); + } + } +}; + +using ReplyUPtr = std::unique_ptr; + +namespace reply { + +template +struct ParseTag {}; + +template +inline T parse(redisReply &reply) { + return parse(ParseTag(), reply); +} + +template +T parse_leniently(redisReply &reply); + +void parse(ParseTag, redisReply &reply); + +std::string parse(ParseTag, redisReply &reply); + +long long parse(ParseTag, redisReply &reply); + +double parse(ParseTag, redisReply &reply); + +bool parse(ParseTag, redisReply &reply); + +template +Optional parse(ParseTag>, redisReply &reply); + +template +std::pair parse(ParseTag>, redisReply &reply); + +template +std::tuple parse(ParseTag>, redisReply &reply); + +#ifdef REDIS_PLUS_PLUS_HAS_VARIANT + +inline Monostate parse(ParseTag, redisReply &) { + // Just ignore the reply + return {}; +} + +template +Variant parse(ParseTag>, redisReply &reply); + +#endif + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template +long long parse_scan_reply(redisReply &reply, Output output); + +inline bool is_error(redisReply &reply) { + return reply.type == REDIS_REPLY_ERROR; +} + +inline bool is_nil(redisReply &reply) { + return reply.type == REDIS_REPLY_NIL; +} + +inline bool is_string(redisReply &reply) { + return reply.type == REDIS_REPLY_STRING; +} + +inline bool is_status(redisReply &reply) { + return reply.type == REDIS_REPLY_STATUS; +} + +inline bool is_integer(redisReply &reply) { + return reply.type == REDIS_REPLY_INTEGER; +} + +inline bool is_array(redisReply &reply) { + return reply.type == REDIS_REPLY_ARRAY; +} + +std::string to_status(redisReply &reply); + +template +void to_array(redisReply &reply, Output output); + +// Rewrite set reply to bool type +void rewrite_set_reply(redisReply &reply); + +// Some command might return an empty array reply as a nil reply, +// e.g. georadius, zpopmin, zpopmax. In this case, we rewrite the +// reply to a nil reply. +void rewrite_empty_array_reply(redisReply &reply); + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple; + +} + +// Inline implementations. + +namespace reply { + +namespace detail { + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr) { + // Empty array. + return; + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null array element reply"); + } + + *output = parse::type>(*sub_reply); + + ++output; + } +} + +bool is_flat_array(redisReply &reply); + +template +void to_flat_array(redisReply &reply, Output output) { + if (reply.element == nullptr) { + // Empty array. + return; + } + + if (reply.elements % 2 != 0) { + throw ProtoError("Not string pair array reply"); + } + + for (std::size_t idx = 0; idx != reply.elements; idx += 2) { + auto *key_reply = reply.element[idx]; + auto *val_reply = reply.element[idx + 1]; + if (key_reply == nullptr || val_reply == nullptr) { + throw ProtoError("Null string array reply"); + } + + using Pair = typename IterType::type; + using FirstType = typename std::decay::type; + using SecondType = typename std::decay::type; + *output = std::make_pair(parse(*key_reply), + parse(*val_reply)); + + ++output; + } +} + +template +void to_array(std::true_type, redisReply &reply, Output output) { + if (is_flat_array(reply)) { + to_flat_array(reply, output); + } else { + to_array(reply, output); + } +} + +template +void to_array(std::false_type, redisReply &reply, Output output) { + to_array(reply, output); +} + +template +std::tuple parse_tuple(redisReply **reply, std::size_t idx) { + assert(reply != nullptr); + + auto *sub_reply = reply[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null reply"); + } + + return std::make_tuple(parse(*sub_reply)); +} + +template +auto parse_tuple(redisReply **reply, std::size_t idx) -> + typename std::enable_if>::type { + assert(reply != nullptr); + + return std::tuple_cat(parse_tuple(reply, idx), + parse_tuple(reply, idx + 1)); +} + +#ifdef REDIS_PLUS_PLUS_HAS_VARIANT + +template +Variant parse_variant(redisReply &reply) { + return parse(reply); +} + +template +auto parse_variant(redisReply &reply) -> + typename std::enable_if>::type { + auto return_var = [](auto &&arg) { + return Variant(std::move(arg)); + }; + + try { + return std::visit(return_var, parse_variant(reply)); + } catch (const ProtoError &) { + return std::visit(return_var, parse_variant(reply)); + } +} + +#endif + +} + +template +T parse_leniently(redisReply &reply) { + if (is_array(reply) && reply.elements == 1) { + if (reply.element == nullptr) { + throw ProtoError("null array reply"); + } + + auto *ele = reply.element[0]; + if (ele != nullptr) { + return parse(*ele); + } // else fall through + } + + return parse(reply); +} + +template +Optional parse(ParseTag>, redisReply &reply) { + if (reply::is_nil(reply)) { + // Because of a GCC bug, we cannot return {} for -std=c++17 + // Refer to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86465 +#if defined REDIS_PLUS_PLUS_HAS_OPTIONAL + return std::nullopt; +#else + return {}; +#endif + } + + return Optional(parse(reply)); +} + +template +std::pair parse(ParseTag>, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != 2) { + throw ProtoError("NOT key-value PAIR reply"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null PAIR reply"); + } + + auto *first = reply.element[0]; + auto *second = reply.element[1]; + if (first == nullptr || second == nullptr) { + throw ProtoError("Null pair reply"); + } + + return std::make_pair(parse::type>(*first), + parse::type>(*second)); +} + +template +std::tuple parse(ParseTag>, redisReply &reply) { + constexpr auto size = sizeof...(Args); + + static_assert(size > 0, "DO NOT support parsing tuple with 0 element"); + + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != size) { + throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null TUPLE reply"); + } + + return detail::parse_tuple(reply.element, 0); +} + +#ifdef REDIS_PLUS_PLUS_HAS_VARIANT + +template +Variant parse(ParseTag>, redisReply &reply) { + return detail::parse_variant(reply); +} + +#endif + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::back_inserter(container)); + + return container; +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::inserter(container, container.end())); + + return container; +} + +template +long long parse_scan_reply(redisReply &reply, Output output) { + if (reply.elements != 2 || reply.element == nullptr) { + throw ProtoError("Invalid scan reply"); + } + + auto *cursor_reply = reply.element[0]; + auto *data_reply = reply.element[1]; + if (cursor_reply == nullptr || data_reply == nullptr) { + throw ProtoError("Invalid cursor reply or data reply"); + } + + auto cursor_str = reply::parse(*cursor_reply); + long long new_cursor = 0; + try { + new_cursor = std::stoll(cursor_str); + } catch (const std::exception &e) { + throw ProtoError("Invalid cursor reply: " + cursor_str); + } + + reply::to_array(*data_reply, output); + + return new_cursor; +} + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + detail::to_array(typename IsKvPairIter::type(), reply, output); +} + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple { + if (!is_array(reply) || reply.elements != 4) { + throw ProtoError("expect array reply with 4 elements"); + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + if (reply.element[idx] == nullptr) { + throw ProtoError("null array reply"); + } + } + + auto num = parse(*(reply.element[0])); + auto start = parse(*(reply.element[1])); + auto end = parse(*(reply.element[2])); + + auto &entry_reply = *(reply.element[3]); + if (!is_nil(entry_reply)) { + to_array(entry_reply, output); + } + + return std::make_tuple(num, std::move(start), std::move(end)); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/sentinel.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/sentinel.h new file mode 100644 index 000000000..6f998791e --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/sentinel.h @@ -0,0 +1,141 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H +#define SEWENEW_REDISPLUSPLUS_SENTINEL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "shards.h" +#include "reply.h" +#include "tls.h" + +namespace sw { + +namespace redis { + +struct SentinelOptions { + std::vector> nodes; + + std::string password; + + bool keep_alive = true; + + std::chrono::milliseconds connect_timeout{100}; + + std::chrono::milliseconds socket_timeout{100}; + + std::chrono::milliseconds retry_interval{100}; + + std::size_t max_retry = 2; + + tls::TlsOptions tls; +}; + +class Sentinel { +public: + explicit Sentinel(const SentinelOptions &sentinel_opts); + + Sentinel(const Sentinel &) = delete; + Sentinel& operator=(const Sentinel &) = delete; + + Sentinel(Sentinel &&) = delete; + Sentinel& operator=(Sentinel &&) = delete; + + ~Sentinel() = default; + +private: + Connection master(const std::string &master_name, const ConnectionOptions &opts); + + Connection slave(const std::string &master_name, const ConnectionOptions &opts); + + class Iterator; + + friend class SimpleSentinel; + + std::list _parse_options(const SentinelOptions &opts) const; + + Optional _get_master_addr_by_name(Connection &connection, const StringView &name); + + std::vector _get_slave_addr_by_name(Connection &connection, const StringView &name); + + Connection _connect_redis(const Node &node, ConnectionOptions opts); + + Role _get_role(Connection &connection); + + std::vector _parse_slave_info(redisReply &reply) const; + + std::list _healthy_sentinels; + + std::list _broken_sentinels; + + SentinelOptions _sentinel_opts; + + std::mutex _mutex; +}; + +class SimpleSentinel { +public: + SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role); + + SimpleSentinel() = default; + + SimpleSentinel(const SimpleSentinel &) = default; + SimpleSentinel& operator=(const SimpleSentinel &) = default; + + SimpleSentinel(SimpleSentinel &&) = default; + SimpleSentinel& operator=(SimpleSentinel &&) = default; + + ~SimpleSentinel() = default; + + explicit operator bool() const { + return bool(_sentinel); + } + + Connection create(const ConnectionOptions &opts); + +private: + std::shared_ptr _sentinel; + + std::string _master_name; + + Role _role = Role::MASTER; +}; + +class StopIterError : public Error { +public: + StopIterError() : Error("StopIterError") {} + + StopIterError(const StopIterError &) = default; + StopIterError& operator=(const StopIterError &) = default; + + StopIterError(StopIterError &&) = default; + StopIterError& operator=(StopIterError &&) = default; + + virtual ~StopIterError() override = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/shards.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/shards.h new file mode 100644 index 000000000..0d4cd97b3 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/shards.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_H + +#include +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +using Slot = std::size_t; + +struct SlotRange { + Slot min; + Slot max; +}; + +inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) { + return lhs.max < rhs.max; +} + +struct Node { + std::string host; + int port; +}; + +inline bool operator==(const Node &lhs, const Node &rhs) { + return lhs.host == rhs.host && lhs.port == rhs.port; +} + +struct NodeHash { + std::size_t operator()(const Node &node) const noexcept { + auto host_hash = std::hash{}(node.host); + auto port_hash = std::hash{}(node.port); + return host_hash ^ (port_hash << 1); + } +}; + +using Shards = std::map; + +class RedirectionError : public ReplyError { +public: + RedirectionError(const std::string &msg); + + RedirectionError(const RedirectionError &) = default; + RedirectionError& operator=(const RedirectionError &) = default; + + RedirectionError(RedirectionError &&) = default; + RedirectionError& operator=(RedirectionError &&) = default; + + virtual ~RedirectionError() override = default; + + Slot slot() const { + return _slot; + } + + const Node& node() const { + return _node; + } + +private: + std::pair _parse_error(const std::string &msg) const; + + Slot _slot = 0; + Node _node; +}; + +class MovedError : public RedirectionError { +public: + explicit MovedError(const std::string &msg) : RedirectionError(msg) {} + + MovedError(const MovedError &) = default; + MovedError& operator=(const MovedError &) = default; + + MovedError(MovedError &&) = default; + MovedError& operator=(MovedError &&) = default; + + virtual ~MovedError() override = default; +}; + +class AskError : public RedirectionError { +public: + explicit AskError(const std::string &msg) : RedirectionError(msg) {} + + AskError(const AskError &) = default; + AskError& operator=(const AskError &) = default; + + AskError(AskError &&) = default; + AskError& operator=(AskError &&) = default; + + virtual ~AskError() override = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/shards_pool.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/shards_pool.h new file mode 100644 index 000000000..e6e9e4a52 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/shards_pool.h @@ -0,0 +1,121 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H + +#include +#include +#include +#include +#include +#include "reply.h" +#include "connection_pool.h" +#include "shards.h" + +namespace sw { + +namespace redis { + +class ShardsPool { +public: + ShardsPool() = default; + + ShardsPool(const ShardsPool &that) = delete; + ShardsPool& operator=(const ShardsPool &that) = delete; + + ShardsPool(ShardsPool &&that); + ShardsPool& operator=(ShardsPool &&that); + + ~ShardsPool() = default; + + ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts, + Role role); + + // Fetch a connection by key. + ConnectionPoolSPtr fetch(const StringView &key); + + // Randomly pick a connection. + ConnectionPoolSPtr fetch(); + + // Fetch a connection by node. + ConnectionPoolSPtr fetch(const Node &node); + + void update(); + + ConnectionOptions connection_options(const StringView &key); + + ConnectionOptions connection_options(); + + Shards shards(); + +private: + void _move(ShardsPool &&that); + + void _init_pool(const Shards &shards); + + Shards _cluster_slots(Connection &connection) const; + + ReplyUPtr _cluster_slots_command(Connection &connection) const; + + Shards _parse_reply(redisReply &reply) const; + + Slot _parse_slot(redisReply *reply) const; + + Node _parse_node(redisReply *reply) const; + + std::pair _parse_slot_info(redisReply &reply) const; + + // Get slot by key. + std::size_t _slot(const StringView &key) const; + + // Randomly pick a slot. + std::size_t _slot() const; + + // Get a random number between [min, max] + std::size_t _random(std::size_t min, std::size_t max) const; + + ConnectionPoolSPtr& _get_pool(Slot slot); + + ConnectionPoolSPtr _fetch(Slot slot); + + ConnectionOptions _connection_options(Slot slot); + + using NodeMap = std::unordered_map; + + NodeMap::iterator _add_node(const Node &node); + + ConnectionPoolOptions _pool_opts; + + ConnectionOptions _connection_opts; + + Shards _shards; + + NodeMap _pools; + + std::mutex _mutex; + + Role _role = Role::MASTER; + + static const std::size_t SHARDS = 16383; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/subscriber.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/subscriber.h new file mode 100644 index 000000000..8b7c5cfb4 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/subscriber.h @@ -0,0 +1,231 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H +#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H + +#include +#include +#include +#include "connection.h" +#include "reply.h" +#include "command.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +// @NOTE: Subscriber is NOT thread-safe. +// Subscriber uses callbacks to handle messages. There are 6 kinds of messages: +// 1) MESSAGE: message sent to a channel. +// 2) PMESSAGE: message sent to channels of a given pattern. +// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel. +// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel. +// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern. +// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern. +// +// Use Subscriber::on_message(MsgCallback) to set the callback function for message of +// *MESSAGE* type, and the callback interface is: +// void (std::string channel, std::string msg) +// +// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of +// *PMESSAGE* type, and the callback interface is: +// void (std::string pattern, std::string channel, std::string msg) +// +// Messages of other types are called *META MESSAGE*, they have the same callback interface. +// Use Subscriber::on_meta(MetaCallback) to set the callback function: +// void (Subscriber::MsgType type, OptionalString channel, long long num) +// +// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to +// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all +// channels/patterns, *channel* will be null. So the second parameter of meta callback +// is of type *OptionalString*. +// +// All these callback interfaces pass std::string by value, and you can take their ownership +// (i.e. std::move) safely. +// +// If you don't set callback for a specific kind of message, Subscriber::consume() will +// receive the message, and ignore it, i.e. no callback will be called. +class Subscriber { +public: + Subscriber(const Subscriber &) = delete; + Subscriber& operator=(const Subscriber &) = delete; + + Subscriber(Subscriber &&) = default; + Subscriber& operator=(Subscriber &&) = default; + + ~Subscriber() = default; + + enum class MsgType { + SUBSCRIBE, + UNSUBSCRIBE, + PSUBSCRIBE, + PUNSUBSCRIBE, + MESSAGE, + PMESSAGE + }; + + template + void on_message(MsgCb msg_callback); + + template + void on_pmessage(PMsgCb pmsg_callback); + + template + void on_meta(MetaCb meta_callback); + + void subscribe(const StringView &channel); + + template + void subscribe(Input first, Input last); + + template + void subscribe(std::initializer_list channels) { + subscribe(channels.begin(), channels.end()); + } + + void unsubscribe(); + + void unsubscribe(const StringView &channel); + + template + void unsubscribe(Input first, Input last); + + template + void unsubscribe(std::initializer_list channels) { + unsubscribe(channels.begin(), channels.end()); + } + + void psubscribe(const StringView &pattern); + + template + void psubscribe(Input first, Input last); + + template + void psubscribe(std::initializer_list channels) { + psubscribe(channels.begin(), channels.end()); + } + + void punsubscribe(); + + void punsubscribe(const StringView &channel); + + template + void punsubscribe(Input first, Input last); + + template + void punsubscribe(std::initializer_list channels) { + punsubscribe(channels.begin(), channels.end()); + } + + void consume(); + +private: + friend class Redis; + + friend class RedisCluster; + + explicit Subscriber(Connection connection); + + MsgType _msg_type(redisReply *reply) const; + + void _check_connection(); + + void _handle_message(redisReply &reply); + + void _handle_pmessage(redisReply &reply); + + void _handle_meta(MsgType type, redisReply &reply); + + using MsgCallback = std::function; + + using PatternMsgCallback = std::function; + + using MetaCallback = std::function; + + using TypeIndex = std::unordered_map; + static const TypeIndex _msg_type_index; + + Connection _connection; + + MsgCallback _msg_callback = nullptr; + + PatternMsgCallback _pmsg_callback = nullptr; + + MetaCallback _meta_callback = nullptr; +}; + +template +void Subscriber::on_message(MsgCb msg_callback) { + _msg_callback = msg_callback; +} + +template +void Subscriber::on_pmessage(PMsgCb pmsg_callback) { + _pmsg_callback = pmsg_callback; +} + +template +void Subscriber::on_meta(MetaCb meta_callback) { + _meta_callback = meta_callback; +} + +template +void Subscriber::subscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::subscribe_range(_connection, first, last); +} + +template +void Subscriber::unsubscribe(Input first, Input last) { + _check_connection(); + + cmd::unsubscribe_range(_connection, first, last); +} + +template +void Subscriber::psubscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::psubscribe_range(_connection, first, last); +} + +template +void Subscriber::punsubscribe(Input first, Input last) { + _check_connection(); + + cmd::punsubscribe_range(_connection, first, last); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/tls.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/tls.h new file mode 100644 index 000000000..0f2303db7 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/tls.h @@ -0,0 +1,47 @@ +/************************************************************************** + Copyright (c) 2020 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_NO_TLS_H +#define SEWENEW_REDISPLUSPLUS_NO_TLS_H + +#include + +namespace sw { + +namespace redis { + +namespace tls { + +struct TlsOptions {}; + +struct TlsContextUPtr {}; + +inline TlsContextUPtr secure_connection(redisContext &/*ctx*/, const TlsOptions &/*opts*/) { + // Do nothing + return {}; +} + +inline bool enabled(const TlsOptions &/*opts*/) { + return false; +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_NO_TLS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/transaction.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/transaction.h new file mode 100644 index 000000000..f19f24889 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/transaction.h @@ -0,0 +1,77 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H +#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H + +#include +#include +#include "connection.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class TransactionImpl { +public: + explicit TransactionImpl(bool piped) : _piped(piped) {} + + template + void command(Connection &connection, Cmd cmd, Args &&...args); + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t cmd_num); + +private: + void _open_transaction(Connection &connection); + + void _close_transaction(); + + void _get_queued_reply(Connection &connection); + + void _get_queued_replies(Connection &connection, std::size_t cmd_num); + + std::vector _exec(Connection &connection); + + void _discard(Connection &connection); + + bool _in_transaction = false; + + bool _piped; +}; + +template +void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + if (!_in_transaction) { + _open_transaction(connection); + } + + cmd(connection, std::forward(args)...); + + if (!_piped) { + _get_queued_reply(connection); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/utils.h b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/utils.h new file mode 100644 index 000000000..f77f796d6 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/include/sw/redis++/utils.h @@ -0,0 +1,193 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H +#define SEWENEW_REDISPLUSPLUS_UTILS_H + +#include +#include +#include +#include "cxx_utils.h" + +namespace sw { + +namespace redis { + +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; + +template +struct IsKvPair : std::false_type {}; + +template +struct IsKvPair> : std::true_type {}; + +template +using Void = void; + +template > +struct IsInserter : std::false_type {}; + +template +//struct IsInserter> : std::true_type {}; +struct IsInserter::value>::type> + : std::true_type {}; + +template > +struct IterType { + using type = typename std::iterator_traits::value_type; +}; + +template +//struct IterType> { +struct IterType::value>::type> { + typename std::enable_if::value>::type> { + using type = typename std::decay::type; +}; + +template > +struct IsIter : std::false_type {}; + +template +struct IsIter::value>::type> : std::true_type {}; + +template +//struct IsIter::iterator_category>> +struct IsIter::value_type>::value>::type> + : std::integral_constant::value> {}; + +template +struct IsKvPairIter : IsKvPair::type> {}; + +template +struct TupleWithType : std::false_type {}; + +template +struct TupleWithType> : std::false_type {}; + +template +struct TupleWithType> : TupleWithType> {}; + +template +struct TupleWithType> : std::true_type {}; + +template +struct IndexSequence {}; + +template +struct MakeIndexSequence : MakeIndexSequence {}; + +template +struct MakeIndexSequence<0, Is...> : IndexSequence {}; + +// NthType and NthValue are taken from +// https://stackoverflow.com/questions/14261183 +template +struct NthType {}; + +template +struct NthType<0, Arg, Args...> { + using type = Arg; +}; + +template +struct NthType { + using type = typename NthType::type; +}; + +template +struct LastType { + using type = typename NthType::type; +}; + +struct InvalidLastType {}; + +template <> +struct LastType<> { + using type = InvalidLastType; +}; + +template +auto NthValue(Arg &&arg, Args &&...) + -> typename std::enable_if<(I == 0), decltype(std::forward(arg))>::type { + return std::forward(arg); +} + +template +auto NthValue(Arg &&, Args &&...args) + -> typename std::enable_if<(I > 0), + decltype(std::forward::type>( + std::declval::type>()))>::type { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template +auto LastValue(Args &&...args) + -> decltype(std::forward::type>( + std::declval::type>())) { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template > +struct HasPushBack : std::false_type {}; + +template +struct HasPushBack().push_back(std::declval()) + )>::value>::type> : std::true_type {}; + +template > +struct HasInsert : std::false_type {}; + +template +struct HasInsert().insert(std::declval(), + std::declval())), + typename T::iterator>::value>::type> : std::true_type {}; + +template +struct IsSequenceContainer + : std::integral_constant::value + && !std::is_same::type, std::string>::value> {}; + +template +struct IsAssociativeContainer + : std::integral_constant::value && !HasPushBack::value> {}; + +uint16_t crc16(const char *buf, int len); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/lib/libredis++.a b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/lib/libredis++.a new file mode 100644 index 000000000..12d829293 Binary files /dev/null and b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/lib/libredis++.a differ diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/lib/pkgconfig/redis++.pc b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/lib/pkgconfig/redis++.pc new file mode 100644 index 000000000..7bf203b35 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/lib/pkgconfig/redis++.pc @@ -0,0 +1,12 @@ +prefix=/home/grant/dev/ZeroTierOne/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64 +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: redis++ +Description: This is a Redis client, based on hiredis and written in C++11. It supports scritpting, pub/sub, pipeline, transaction, Redis Cluster, Redis Sentinel, connection pool, ACL, SSL and thread safety. +Version: 1.3.3 +URL: https://github.com/sewenew/redis-plus-plus +Requires: hiredis +Cflags: -I${includedir} +Libs: -L${libdir} -lredis++ diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-config-version.cmake b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-config-version.cmake new file mode 100644 index 000000000..ef4be219f --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-config-version.cmake @@ -0,0 +1,48 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version. +# The variable CVF_VERSION must be set before calling configure_file(). + +set(PACKAGE_VERSION "1.3.3") + +if (PACKAGE_FIND_VERSION_RANGE) + # Package version must be in the requested version range + if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN) + OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX))) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + endif() +else() + if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() + endif() +endif() + + +# if the installed project requested no architecture check, don't perform the check +if("FALSE") + return() +endif() + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-config.cmake b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-config.cmake new file mode 100644 index 000000000..580d98613 --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-config.cmake @@ -0,0 +1,36 @@ + +####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() ####### +####### Any changes to this file will be overwritten by the next CMake run #### +####### The input file was redis++-config.cmake.in ######## + +get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE) + +macro(set_and_check _var _file) + set(${_var} "${_file}") + if(NOT EXISTS "${_file}") + message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !") + endif() +endmacro() + +macro(check_required_components _NAME) + foreach(comp ${${_NAME}_FIND_COMPONENTS}) + if(NOT ${_NAME}_${comp}_FOUND) + if(${_NAME}_FIND_REQUIRED_${comp}) + set(${_NAME}_FOUND FALSE) + endif() + endif() + endforeach() +endmacro() + +#################################################################################### + +include(CMakeFindDependencyMacro) + +string(REPLACE "," ";" REDIS_PLUS_PLUS_DEPENDS_LIST hiredis) +foreach(REDIS_PLUS_PLUS_DEP ${REDIS_PLUS_PLUS_DEPENDS_LIST}) + find_dependency(${REDIS_PLUS_PLUS_DEP} REQUIRED) +endforeach() + +include("${CMAKE_CURRENT_LIST_DIR}/redis++-targets.cmake") + +check_required_components(redis++) diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-targets-release.cmake b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-targets-release.cmake new file mode 100644 index 000000000..b790b539f --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-targets-release.cmake @@ -0,0 +1,19 @@ +#---------------------------------------------------------------- +# Generated CMake target import file for configuration "Release". +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "redis++::redis++_static" for configuration "Release" +set_property(TARGET redis++::redis++_static APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(redis++::redis++_static PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libredis++.a" + ) + +list(APPEND _IMPORT_CHECK_TARGETS redis++::redis++_static ) +list(APPEND _IMPORT_CHECK_FILES_FOR_redis++::redis++_static "${_IMPORT_PREFIX}/lib/libredis++.a" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-targets.cmake b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-targets.cmake new file mode 100644 index 000000000..9e87d927e --- /dev/null +++ b/ext/redis-plus-plus-1.3.3/install/ubuntu22.04/arm64/share/cmake/redis++/redis++-targets.cmake @@ -0,0 +1,94 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.6) + message(FATAL_ERROR "CMake >= 2.6.0 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.6...3.20) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_targetsDefined) +set(_targetsNotDefined) +set(_expectedTargets) +foreach(_expectedTarget redis++::redis++_static) + list(APPEND _expectedTargets ${_expectedTarget}) + if(NOT TARGET ${_expectedTarget}) + list(APPEND _targetsNotDefined ${_expectedTarget}) + endif() + if(TARGET ${_expectedTarget}) + list(APPEND _targetsDefined ${_expectedTarget}) + endif() +endforeach() +if("${_targetsDefined}" STREQUAL "${_expectedTargets}") + unset(_targetsDefined) + unset(_targetsNotDefined) + unset(_expectedTargets) + set(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT "${_targetsDefined}" STREQUAL "") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") +endif() +unset(_targetsDefined) +unset(_targetsNotDefined) +unset(_expectedTargets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target redis++::redis++_static +add_library(redis++::redis++_static STATIC IMPORTED) + +set_target_properties(redis++::redis++_static PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include" +) + +# Load information for each installed configuration. +get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(GLOB CONFIG_FILES "${_DIR}/redis++-targets-*.cmake") +foreach(f ${CONFIG_FILES}) + include(${f}) +endforeach() + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(target ${_IMPORT_CHECK_TARGETS} ) + foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} ) + if(NOT EXISTS "${file}" ) + message(FATAL_ERROR "The imported target \"${target}\" references the file + \"${file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + unset(_IMPORT_CHECK_FILES_FOR_${target}) +endforeach() +unset(_IMPORT_CHECK_TARGETS) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/make-linux.mk b/make-linux.mk index ede843c5f..6702d040d 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -119,6 +119,9 @@ ifeq ($(CC_MACH),x86_64) override CFLAGS+=-msse -msse2 override CXXFLAGS+=-msse -msse2 ZT_SSO_SUPPORTED=1 + ifeq ($(ZT_CONTROLLER),1) + EXT_ARCH=amd64 + endif endif ifeq ($(CC_MACH),amd64) ZT_ARCHITECTURE=2 @@ -127,6 +130,9 @@ ifeq ($(CC_MACH),amd64) override CFLAGS+=-msse -msse2 override CXXFLAGS+=-msse -msse2 ZT_SSO_SUPPORTED=1 + ifeq ($(ZT_CONTROLLER),1) + EXT_ARCH=amd64 + endif endif ifeq ($(CC_MACH),powerpc64le) ZT_ARCHITECTURE=8 @@ -229,12 +235,17 @@ endif ifeq ($(CC_MACH),arm64) ZT_ARCHITECTURE=4 ZT_SSO_SUPPORTED=1 + ZT_USE_X64_ASM_ED25519=0 override DEFS+=-DZT_NO_TYPE_PUNNING -DZT_ARCH_ARM_HAS_NEON -march=armv8-a+crypto -mtune=generic -mstrict-align endif ifeq ($(CC_MACH),aarch64) ZT_ARCHITECTURE=4 ZT_SSO_SUPPORTED=1 + ZT_USE_X64_ASM_ED25519=0 override DEFS+=-DZT_NO_TYPE_PUNNING -DZT_ARCH_ARM_HAS_NEON -march=armv8-a+crypto -mtune=generic -mstrict-align + ifeq ($(ZT_CONTROLLER),1) + EXT_ARCH=arm64 + endif endif ifeq ($(CC_MACH),mipsel) ZT_ARCHITECTURE=5 @@ -310,9 +321,9 @@ endif ifeq ($(ZT_CONTROLLER),1) override CXXFLAGS+=-Wall -Wno-deprecated -std=c++17 -pthread $(INCLUDES) -DNDEBUG $(DEFS) - override LDLIBS+=-Lext/libpqxx-7.7.3/install/ubuntu22.04/lib -lpqxx -lpq ext/hiredis-1.0.2/lib/ubuntu22.04/libhiredis.a ext/redis-plus-plus-1.3.3/install/ubuntu22.04/lib/libredis++.a -lssl -lcrypto - override DEFS+=-DZT_CONTROLLER_USE_LIBPQ - override INCLUDES+=-I/usr/include/postgresql -Iext/libpqxx-7.7.3/install/ubuntu22.04/include -Iext/hiredis-1.0.2/include/ -Iext/redis-plus-plus-1.3.3/install/ubuntu22.04/include/sw/ + override LDLIBS+=-Lext/libpqxx-7.7.3/install/ubuntu22.04/$(EXT_ARCH)/lib -lpqxx -lpq ext/hiredis-1.0.2/lib/ubuntu22.04/$(EXT_ARCH)/libhiredis.a ext/redis-plus-plus-1.3.3/install/ubuntu22.04/$(EXT_ARCH)/lib/libredis++.a -lssl -lcrypto + override DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_NO_PEER_METRICS + override INCLUDES+=-I/usr/include/postgresql -Iext/libpqxx-7.7.3/install/ubuntu22.04/$(EXT_ARCH)/include -Iext/hiredis-1.0.2/include/ -Iext/redis-plus-plus-1.3.3/install/ubuntu22.04/$(EXT_ARCH)/include/sw/ endif # ARM32 hell -- use conservative CFLAGS @@ -397,11 +408,19 @@ official: FORCE docker: FORCE docker build --no-cache -f ext/installfiles/linux/zerotier-containerized/Dockerfile -t zerotier-containerized . -central-controller: FORCE - make -j4 ZT_CONTROLLER=1 ZT_USE_X64_ASM_ED25519=1 one +_buildx: + @echo "docker buildx create" + # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + docker run --privileged --rm tonistiigi/binfmt --install all + @echo docker buildx create --name multiarch --driver docker-container --use + @echo docker buildx inspect --bootstrap -central-controller-docker: FORCE - docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . +central-controller: FORCE + make -j4 ZT_CONTROLLER=1 one + +central-controller-docker: _buildx FORCE + docker buildx build --platform linux/amd64,linux/arm64 --no-cache -t registry.zerotier.com/zerotier/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . --push + @echo Image: registry.zerotier.com/zerotier/ztcentral-controller:${TIMESTAMP} debug: FORCE make ZT_DEBUG=1 one diff --git a/make-mac.mk b/make-mac.mk index 20656f69b..33ecc8f09 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -176,9 +176,17 @@ official: FORCE make ZT_OFFICIAL_RELEASE=1 -j 8 one make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg -central-controller-docker: FORCE - docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . +_buildx: + @echo "docker buildx create" + # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + docker run --privileged --rm tonistiigi/binfmt --install all + @echo docker buildx create --name multiarch --driver docker-container --use + @echo docker buildx inspect --bootstrap +central-controller-docker: _buildx FORCE + docker buildx build --platform linux/arm64,linux/amd64 --no-cache -t registry.zerotier.com/zerotier/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . --push + @echo Image: registry.zerotier.com/zerotier/ztcentral-controller:${TIMESTAMP} + clean: rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* zeroidc/target/ diff --git a/node/AES.cpp b/node/AES.cpp index 8f2f30d20..90247b468 100644 --- a/node/AES.cpp +++ b/node/AES.cpp @@ -136,8 +136,9 @@ void AES::GMAC::update(const void *const data, unsigned int len) noexcept if (_rp) { for (;;) { - if (!len) + if (!len) { return; + } --len; _r[_rp++] = *(in++); if (_rp == 16) { @@ -160,8 +161,9 @@ void AES::GMAC::update(const void *const data, unsigned int len) noexcept _y[0] = y0; _y[1] = y1; - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { _r[i] = in[i]; + } _rp = len; // len is always less than 16 here } @@ -187,8 +189,9 @@ void AES::GMAC::finish(uint8_t tag[16]) noexcept uint64_t y1 = _y[1]; if (_rp) { - while (_rp < 16) + while (_rp < 16) { _r[_rp++] = 0; + } y0 ^= Utils::loadMachineEndian< uint64_t >(_r); y1 ^= Utils::loadMachineEndian< uint64_t >(_r + 8); s_gfmul(h0, h1, y0, y1); @@ -247,8 +250,9 @@ void AES::CTR::crypt(const void *const input, unsigned int len) noexcept _aes.p_encryptSW(reinterpret_cast(_ctr), reinterpret_cast(keyStream)); reinterpret_cast(_ctr)[3] = Utils::hton(++ctr); uint8_t *outblk = out + (totalLen - 16); - for (int i = 0; i < 16; ++i) + for (int i = 0; i < 16; ++i) { outblk[i] ^= reinterpret_cast(keyStream)[i]; + } break; } } @@ -442,8 +446,9 @@ void AES::CTR::finish() noexcept const unsigned int rem = _len & 15U; if (rem) { _aes.encrypt(_ctr, tmp); - for (unsigned int i = 0, j = _len - rem; i < rem; ++i) + for (unsigned int i = 0, j = _len - rem; i < rem; ++i) { _out[j + i] ^= tmp[i]; + } } } @@ -497,8 +502,9 @@ void AES::p_initSW(const uint8_t *key) noexcept rk[9] = rk[1] ^ rk[8]; rk[10] = rk[2] ^ rk[9]; rk[11] = rk[3] ^ rk[10]; - if (++i == 7) + if (++i == 7) { break; + } temp = rk[11]; rk[12] = rk[4] ^ (Te2_r(temp >> 24U) & 0xff000000U) ^ (Te3_r((temp >> 16U) & 0xffU) & 0x00ff0000U) ^ (Te0[(temp >> 8U) & 0xffU] & 0x0000ff00U) ^ (Te1_r((temp) & 0xffU) & 0x000000ffU); rk[13] = rk[5] ^ rk[12]; @@ -511,8 +517,9 @@ void AES::p_initSW(const uint8_t *key) noexcept p_k.sw.h[0] = Utils::ntoh(p_k.sw.h[0]); p_k.sw.h[1] = Utils::ntoh(p_k.sw.h[1]); - for (int i = 0; i < 60; ++i) + for (int i = 0; i < 60; ++i) { p_k.sw.dk[i] = p_k.sw.ek[i]; + } rk = p_k.sw.dk; for (int i = 0, j = 56; i < j; i += 4, j -= 4) { diff --git a/node/AES.hpp b/node/AES.hpp index b8f4ef996..0cae08656 100644 --- a/node/AES.hpp +++ b/node/AES.hpp @@ -197,8 +197,9 @@ public: *reinterpret_cast(_iv + 8) = *reinterpret_cast(iv + 8); *reinterpret_cast(_iv + 12) = 0x01000000; // 0x00000001 in big-endian byte order #else - for(int i=0;i<12;++i) + for(int i=0;i<12;++i) { _iv[i] = iv[i]; + } _iv[12] = 0; _iv[13] = 0; _iv[14] = 0; @@ -373,8 +374,9 @@ public: // End of AAD is padded to a multiple of 16 bytes to ensure unique encoding. len &= 0xfU; - if (len != 0) + if (len != 0) { _gmac.update(Utils::ZERO256, 16 - len); + } } /** @@ -495,8 +497,9 @@ public: { _gmac.update(aad, len); len &= 0xfU; - if (len != 0) + if (len != 0) { _gmac.update(Utils::ZERO256, 16 - len); + } } /** diff --git a/node/AES_aesni.cpp b/node/AES_aesni.cpp index a185b1b36..31b046608 100644 --- a/node/AES_aesni.cpp +++ b/node/AES_aesni.cpp @@ -228,8 +228,9 @@ void AES::GMAC::p_aesNIUpdate(const uint8_t *in, unsigned int len) noexcept // Handle anything left over from a previous run that wasn't a multiple of 16 bytes. if (_rp) { for (;;) { - if (!len) + if (!len) { return; + } --len; _r[_rp++] = *(in++); if (_rp == 16) { @@ -281,8 +282,9 @@ void AES::GMAC::p_aesNIUpdate(const uint8_t *in, unsigned int len) noexcept _mm_storeu_si128(reinterpret_cast<__m128i *>(_y), y); // Any overflow is cached for a later run or finish(). - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { _r[i] = in[i]; + } _rp = len; // len is always less than 16 here } @@ -295,8 +297,9 @@ void AES::GMAC::p_aesNIFinish(uint8_t tag[16]) noexcept // Handle any remaining bytes, padding the last block with zeroes. if (_rp) { - while (_rp < 16) + while (_rp < 16) { _r[_rp++] = 0; + } y = p_gmacPCLMUL128(_aes.p_k.ni.h[0], _mm_xor_si128(y, _mm_loadu_si128(reinterpret_cast<__m128i *>(_r)))); } @@ -438,9 +441,9 @@ void AES::CTR::p_aesNICrypt(const uint8_t *in, uint8_t *out, unsigned int len) n #if !defined(ZT_AES_VAES512) && defined(ZT_AES_VAES256) if (Utils::CPUID.vaes && (len >= 256)) { - p_aesCtrInnerVAES256(len, _ctr[0], c1, in, out, k); - goto skip_conventional_aesni_64; - } + p_aesCtrInnerVAES256(len, _ctr[0], c1, in, out, k); + goto skip_conventional_aesni_64; + } #endif const uint8_t *const eof64 = in + (len & ~((unsigned int)63)); @@ -552,8 +555,9 @@ void AES::CTR::p_aesNICrypt(const uint8_t *in, uint8_t *out, unsigned int len) n // Any remaining input is placed in _out. This will be picked up and crypted // on subsequent calls to crypt() or finish() as it'll mean _len will not be // an even multiple of 16. - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { out[i] = in[i]; + } _ctr[1] = Utils::hton(c1); } diff --git a/node/AES_armcrypto.cpp b/node/AES_armcrypto.cpp index c77aa0779..2f20c23de 100644 --- a/node/AES_armcrypto.cpp +++ b/node/AES_armcrypto.cpp @@ -56,8 +56,9 @@ void AES::GMAC::p_armUpdate(const uint8_t *in, unsigned int len) noexcept if (_rp) { for(;;) { - if (!len) + if (!len) { return; + } --len; _r[_rp++] = *(in++); if (_rp == 16) { @@ -75,8 +76,9 @@ void AES::GMAC::p_armUpdate(const uint8_t *in, unsigned int len) noexcept vst1q_u8(reinterpret_cast(_y), y); - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { _r[i] = in[i]; + } _rp = len; // len is always less than 16 here } @@ -87,8 +89,9 @@ void AES::GMAC::p_armFinish(uint8_t tag[16]) noexcept const uint8x16_t h = _aes.p_k.neon.h; if (_rp) { - while (_rp < 16) + while (_rp < 16) { _r[_rp++] = 0; + } y = s_clmul_armneon_crypto(h, y, _r); } @@ -255,8 +258,9 @@ void AES::CTR::p_armCrypt(const uint8_t *in, uint8_t *out, unsigned int len) noe in += 64; dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, four); - if (unlikely(len < 64)) + if (unlikely(len < 64)) { break; + } dd1 = (uint8x16_t)vaddq_u32((uint32x4_t)dd1, four); dd2 = (uint8x16_t)vaddq_u32((uint32x4_t)dd2, four); dd3 = (uint8x16_t)vaddq_u32((uint32x4_t)dd3, four); @@ -290,8 +294,9 @@ void AES::CTR::p_armCrypt(const uint8_t *in, uint8_t *out, unsigned int len) noe // Any remaining input is placed in _out. This will be picked up and crypted // on subsequent calls to crypt() or finish() as it'll mean _len will not be // an even multiple of 16. - for (unsigned int i = 0; i < len; ++i) + for (unsigned int i = 0; i < len; ++i) { out[i] = in[i]; + } vst1q_u8(reinterpret_cast(_ctr), vrev32q_u8(dd)); } @@ -327,12 +332,14 @@ void AES::p_init_armneon_crypto(const uint8_t *key) noexcept w[i] = w[i - ZT_INIT_ARMNEON_CRYPTO_NK] ^ t; } - for (unsigned int i=0;i<(ZT_INIT_ARMNEON_CRYPTO_NB * (ZT_INIT_ARMNEON_CRYPTO_NR + 1));++i) + for (unsigned int i=0;i<(ZT_INIT_ARMNEON_CRYPTO_NB * (ZT_INIT_ARMNEON_CRYPTO_NR + 1));++i) { w[i] = Utils::hton(w[i]); + } p_k.neon.dk[0] = p_k.neon.ek[14]; - for (int i=1;i<14;++i) + for (int i=1;i<14;++i) { p_k.neon.dk[i] = vaesimcq_u8(p_k.neon.ek[14 - i]); + } p_k.neon.dk[14] = p_k.neon.ek[0]; p_encrypt_armneon_crypto(Utils::ZERO256, h); diff --git a/node/Address.hpp b/node/Address.hpp index d72eb6af6..b6ccd86d7 100644 --- a/node/Address.hpp +++ b/node/Address.hpp @@ -71,8 +71,9 @@ public: */ inline void copyTo(void *const bits,const unsigned int len) const { - if (len < ZT_ADDRESS_LENGTH) + if (len < ZT_ADDRESS_LENGTH) { return; + } unsigned char *b = (unsigned char *)bits; *(b++) = (unsigned char)((_a >> 32) & 0xff); *(b++) = (unsigned char)((_a >> 24) & 0xff); diff --git a/node/Bond.cpp b/node/Bond.cpp index 8edd7e297..21a8a5638 100644 --- a/node/Bond.cpp +++ b/node/Bond.cpp @@ -112,13 +112,11 @@ SharedPtr Bond::createBond(const RuntimeEnvironment* renv, const SharedPtr bond = new Bond(renv, _bondPolicyTemplates[_defaultPolicyStr].ptr(), peer); bond->debug("new default custom bond (based on %s)", bond->getPolicyStrByCode(bond->policy()).c_str()); } - } - else { + } else { if (! _bondPolicyTemplates[_policyTemplateAssignments[identity]]) { bond = new Bond(renv, _defaultPolicy, peer); bond->debug("peer-specific bond, was specified as %s but the bond definition was not found, using default %s", _policyTemplateAssignments[identity].c_str(), getPolicyStrByCode(_defaultPolicy).c_str()); - } - else { + } else { bond = new Bond(renv, _bondPolicyTemplates[_policyTemplateAssignments[identity]].ptr(), peer); bond->debug("new default bond"); } @@ -187,12 +185,10 @@ SharedPtr Bond::getLinkBySocket(const std::string& policyAlias, uint64_t l SharedPtr s = new Link(ifnameStr, 0, 0, 0, true, ZT_BOND_SLAVE_MODE_PRIMARY, ""); _interfaceToLinkMap[policyAlias].insert(std::pair >(ifnameStr, s)); return s; - } - else { + } else { return SharedPtr(); } - } - else { + } else { return search->second; } } @@ -359,8 +355,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) _rrPacketsSentOnCurrLink = 0; if (_numBondedPaths == 1 || _rrIdx >= (ZT_MAX_PEER_NETWORK_PATHS - 1)) { _rrIdx = 0; - } - else { + } else { int _tempIdx = _rrIdx; for (int searchCount = 0; searchCount < (_numBondedPaths - 1); searchCount++) { _tempIdx = (_tempIdx == (_numBondedPaths - 1)) ? 0 : _tempIdx + 1; @@ -390,8 +385,7 @@ SharedPtr Bond::getAppropriatePath(int64_t now, int32_t flowId) if (likely(it != _flows.end())) { it->second->lastActivity = now; return _paths[it->second->assignedPath].p; - } - else { + } else { unsigned char entropy; Utils::getSecureRandom(&entropy, 1); SharedPtr flow = createFlow(ZT_MAX_PEER_NETWORK_PATHS, flowId, entropy, now); @@ -469,8 +463,7 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, _paths[pathIdx].qosStatsIn[packetId] = now; ++(_paths[pathIdx].packetsReceivedSinceLastQoS); //_paths[pathIdx].packetValiditySamples.push(true); - } - else { + } else { // debug("QoS buffer full, will not record information"); } /* @@ -497,8 +490,7 @@ void Bond::recordIncomingPacket(const SharedPtr& path, uint64_t packetId, SharedPtr flow; if (! _flows.count(flowId)) { flow = createFlow(pathIdx, flowId, 0, now); - } - else { + } else { flow = _flows[flowId]; } if (flow) { @@ -584,8 +576,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr& flow, int64_t now, bool reass if (reassign) { log("attempting to re-assign out-flow %04x previously on idx %d (%u / %zu flows)", flow->id, flow->assignedPath, _paths[_realIdxMap[flow->assignedPath]].assignedFlowCount, _flows.size()); - } - else { + } else { debug("attempting to assign flow for the first time"); } @@ -599,8 +590,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr& flow, int64_t now, bool reass if (reassign) { bondedIdx = (flow->assignedPath + offset) % (_numBondedPaths); - } - else { + } else { bondedIdx = abs((int)((entropy + offset) % (_numBondedPaths))); } // debug("idx=%d, offset=%d, randomCap=%f, actualCap=%f", bondedIdx, offset, randomLinkCapacity, _paths[_realIdxMap[bondedIdx]].relativeLinkCapacity); @@ -623,8 +613,7 @@ bool Bond::assignFlowToBondedPath(SharedPtr& flow, int64_t now, bool reass flow->assignPath(_realIdxMap[bondedIdx], now); ++(_paths[_realIdxMap[bondedIdx]].assignedFlowCount); // debug(" ABLE to find optimal link %f idx %d", _paths[_realIdxMap[bondedIdx]].relativeQuality, bondedIdx); - } - else { + } else { // We were (unable) to find a path that didn't violate at least one quality requirement, will choose next best option flow->assignPath(_realIdxMap[nextBestQualIdx], now); ++(_paths[_realIdxMap[nextBestQualIdx]].assignedFlowCount); @@ -684,13 +673,11 @@ void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now) debug("forget flow %04x (age %" PRId64 ") (%u / %zu)", it->first, it->second->age(now), _paths[it->second->assignedPath].assignedFlowCount, (_flows.size() - 1)); _paths[it->second->assignedPath].assignedFlowCount--; it = _flows.erase(it); - } - else { + } else { ++it; } } - } - else if (oldest) { // Remove single oldest by natural expiration + } else if (oldest) { // Remove single oldest by natural expiration uint64_t maxAge = 0; while (it != _flows.end()) { if (it->second->age(now) > maxAge) { @@ -737,8 +724,7 @@ void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr& if (_peer->_id.address().toInt() > RR->node->identity().address().toInt()) { debug("agree with peer to use alternate link %s/%s\n", link->ifname().c_str(), pathStr); _negotiatedPathIdx = pathIdx; - } - else { + } else { debug("ignore petition from peer to use alternate link %s/%s\n", link->ifname().c_str(), pathStr); } } @@ -809,6 +795,7 @@ void Bond::sendPATH_NEGOTIATION_REQUEST(void* tPtr, int pathIdx) Packet outp(_peer->_id.address(), RR->identity.address(), Packet::VERB_PATH_NEGOTIATION_REQUEST); outp.append(_localUtility); if (_paths[pathIdx].p->address()) { + Metrics::pkt_path_negotiation_request_out++; outp.armor(_peer->key(), false, _peer->aesKeysIfSupported()); RR->node->putPacket(tPtr, _paths[pathIdx].p->localSocket(), _paths[pathIdx].p->address(), outp.data(), outp.size()); _overheadBytes += outp.size(); @@ -852,10 +839,10 @@ void Bond::sendQOS_MEASUREMENT(void* tPtr, int pathIdx, int64_t localSocket, con if (atAddress) { outp.armor(_peer->key(), false, _peer->aesKeysIfSupported()); RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size()); - } - else { + } else { RR->sw->send(tPtr, outp, false); } + Metrics::pkt_qos_out++; _paths[pathIdx].packetsReceivedSinceLastQoS = 0; _paths[pathIdx].lastQoSMeasurement = now; _overheadBytes += outp.size(); @@ -892,6 +879,7 @@ void Bond::processBackgroundBondTasks(void* tPtr, int64_t now) RR->node->putPacket(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), outp.data(), outp.size()); _paths[i].p->_lastOut = now; _overheadBytes += outp.size(); + Metrics::pkt_echo_out++; // debug("tx: verb 0x%-2x of len %4d via %s (ECHO)", Packet::VERB_ECHO, outp.size(), pathToStr(_paths[i].p).c_str()); } } @@ -1192,8 +1180,7 @@ void Bond::estimatePathQuality(int64_t now) if ((now - it->second) >= qosRecordTimeout) { it = _paths[i].qosStatsOut.erase(it); ++numDroppedQosOutRecords; - } - else { + } else { ++it; } } @@ -1221,8 +1208,7 @@ void Bond::estimatePathQuality(int64_t now) if ((now - it->second) >= qosRecordTimeout) { it = _paths[i].qosStatsIn.erase(it); ++numDroppedQosInRecords; - } - else { + } else { ++it; } } @@ -1341,8 +1327,7 @@ void Bond::estimatePathQuality(int64_t now) shouldAvoid = true; } _paths[i].shouldAvoid = shouldAvoid; - } - else { + } else { if (! shouldAvoid) { log("no longer avoiding link %s", pathToStr(_paths[i].p).c_str()); _paths[i].shouldAvoid = false; @@ -1454,8 +1439,7 @@ void Bond::processActiveBackupTasks(void* tPtr, int64_t now) _lastBondStatusLog = now; if (_abPathIdx == ZT_MAX_PEER_NETWORK_PATHS) { log("no active link"); - } - else if (_paths[_abPathIdx].p) { + } else if (_paths[_abPathIdx].p) { log("active link is %s, failover queue size is %zu", pathToStr(_paths[_abPathIdx].p).c_str(), _abFailoverQueue.size()); } if (_abFailoverQueue.empty()) { @@ -1563,8 +1547,7 @@ void Bond::processActiveBackupTasks(void* tPtr, int64_t now) log("link %s is ineligible, removing from failover queue (%zu links remain in queue)", pathToStr(_paths[_abPathIdx].p).c_str(), _abFailoverQueue.size()); } continue; - } - else { + } else { ++it; } } @@ -1713,8 +1696,7 @@ void Bond::processActiveBackupTasks(void* tPtr, int64_t now) if (! _abFailoverQueue.empty()) { dequeueNextActiveBackupPath(now); log("active link switched to %s", pathToStr(_paths[_abPathIdx].p).c_str()); - } - else { + } else { log("failover queue is empty, no links to choose from"); } } @@ -1760,8 +1742,7 @@ void Bond::processActiveBackupTasks(void* tPtr, int64_t now) dequeueNextActiveBackupPath(now); _lastPathNegotiationCheck = now; log("switch negotiated link %s (select mode: optimize)", pathToStr(_paths[_abPathIdx].p).c_str()); - } - else { + } else { // Try to find a better path and automatically switch to it -- not too often, though. if ((now - _lastActiveBackupPathChange) > ZT_BOND_OPTIMIZE_INTERVAL) { if (! _abFailoverQueue.empty()) { diff --git a/node/Bond.hpp b/node/Bond.hpp index 389b970e9..81b4691b5 100644 --- a/node/Bond.hpp +++ b/node/Bond.hpp @@ -889,8 +889,7 @@ class Bond { _lastAckRateCheck = now; if (_ackCutoffCount > numToDrain) { _ackCutoffCount -= numToDrain; - } - else { + } else { _ackCutoffCount = 0; } return (_ackCutoffCount < ZT_ACK_CUTOFF_LIMIT); @@ -909,8 +908,7 @@ class Bond { uint64_t diff = now - _lastQoSRateCheck; if ((diff) <= (_qosSendInterval / ZT_MAX_PEER_NETWORK_PATHS)) { ++_qosCutoffCount; - } - else { + } else { _qosCutoffCount = 0; } _lastQoSRateCheck = now; @@ -930,8 +928,7 @@ class Bond { int diff = now - _lastPathNegotiationReceived; if ((diff) <= (ZT_PATH_NEGOTIATION_CUTOFF_TIME / ZT_MAX_PEER_NETWORK_PATHS)) { ++_pathNegotiationCutoffCount; - } - else { + } else { _pathNegotiationCutoffCount = 0; } _lastPathNegotiationReceived = now; @@ -1228,20 +1225,17 @@ class Bond { unsigned int suggestedRefractoryPeriod = refractoryPeriod ? punishment + (refractoryPeriod * 2) : punishment; refractoryPeriod = std::min(suggestedRefractoryPeriod, (unsigned int)ZT_BOND_MAX_REFRACTORY_PERIOD); lastRefractoryUpdate = 0; - } - else { + } else { uint32_t drainRefractory = 0; if (lastRefractoryUpdate) { drainRefractory = (now - lastRefractoryUpdate); - } - else { + } else { drainRefractory = (now - lastAliveToggle); } lastRefractoryUpdate = now; if (refractoryPeriod > drainRefractory) { refractoryPeriod -= drainRefractory; - } - else { + } else { refractoryPeriod = 0; lastRefractoryUpdate = 0; } diff --git a/node/Buffer.hpp b/node/Buffer.hpp index a8ca82ed8..7211f1879 100644 --- a/node/Buffer.hpp +++ b/node/Buffer.hpp @@ -81,8 +81,9 @@ public: Buffer(unsigned int l) { - if (l > C) + if (l > C) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } _l = l; } @@ -100,8 +101,9 @@ public: template inline Buffer &operator=(const Buffer &b) { - if (unlikely(b._l > C)) + if (unlikely(b._l > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } if (C2 == C) { memcpy(this,&b,sizeof(Buffer)); } else { @@ -112,23 +114,26 @@ public: inline void copyFrom(const void *b,unsigned int l) { - if (unlikely(l > C)) + if (unlikely(l > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } memcpy(_b,b,l); _l = l; } unsigned char operator[](const unsigned int i) const { - if (unlikely(i >= _l)) + if (unlikely(i >= _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } return (unsigned char)_b[i]; } unsigned char &operator[](const unsigned int i) { - if (unlikely(i >= _l)) + if (unlikely(i >= _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } return ((unsigned char *)_b)[i]; } @@ -147,14 +152,16 @@ public: */ unsigned char *field(unsigned int i,unsigned int l) { - if (unlikely((i + l) > _l)) + if (unlikely((i + l) > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } return (unsigned char *)(_b + i); } const unsigned char *field(unsigned int i,unsigned int l) const { - if (unlikely((i + l) > _l)) + if (unlikely((i + l) > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } return (const unsigned char *)(_b + i); } @@ -168,12 +175,14 @@ public: template inline void setAt(unsigned int i,const T v) { - if (unlikely((i + sizeof(T)) > _l)) + if (unlikely((i + sizeof(T)) > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } #ifdef ZT_NO_TYPE_PUNNING uint8_t *p = reinterpret_cast(_b + i); - for(unsigned int x=1;x<=sizeof(T);++x) + for(unsigned int x=1;x<=sizeof(T);++x) { *(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x))); + } #else T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast(_b + i); *p = Utils::hton(v); @@ -190,8 +199,9 @@ public: template inline T at(unsigned int i) const { - if (unlikely((i + sizeof(T)) > _l)) + if (unlikely((i + sizeof(T)) > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } #ifdef ZT_NO_TYPE_PUNNING T v = 0; const uint8_t *p = reinterpret_cast(_b + i); @@ -216,12 +226,14 @@ public: template inline void append(const T v) { - if (unlikely((_l + sizeof(T)) > C)) + if (unlikely((_l + sizeof(T)) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } #ifdef ZT_NO_TYPE_PUNNING uint8_t *p = reinterpret_cast(_b + _l); - for(unsigned int x=1;x<=sizeof(T);++x) + for(unsigned int x=1;x<=sizeof(T);++x) { *(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x))); + } #else T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast(_b + _l); *p = Utils::hton(v); @@ -238,10 +250,12 @@ public: */ inline void append(unsigned char c,unsigned int n) { - if (unlikely((_l + n) > C)) + if (unlikely((_l + n) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; - for(unsigned int i=0;i C)) + if (unlikely((_l + n) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } Utils::getSecureRandom(_b + _l,n); _l += n; } @@ -266,8 +281,9 @@ public: */ inline void append(const void *b,unsigned int l) { - if (unlikely((_l + l) > C)) + if (unlikely((_l + l) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } memcpy(_b + _l,b,l); _l += l; } @@ -281,10 +297,12 @@ public: inline void appendCString(const char *s) { for(;;) { - if (unlikely(_l >= C)) + if (unlikely(_l >= C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; - if (!(_b[_l++] = *(s++))) + } + if (!(_b[_l++] = *(s++))) { break; + } } } @@ -313,8 +331,9 @@ public: */ inline char *appendField(unsigned int l) { - if (unlikely((_l + l) > C)) + if (unlikely((_l + l) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } char *r = _b + _l; _l += l; return r; @@ -330,8 +349,9 @@ public: */ inline void addSize(unsigned int i) { - if (unlikely((i + _l) > C)) + if (unlikely((i + _l) > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } _l += i; } @@ -345,8 +365,9 @@ public: */ inline void setSize(const unsigned int i) { - if (unlikely(i > C)) + if (unlikely(i > C)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } _l = i; } @@ -358,10 +379,12 @@ public: */ inline void behead(const unsigned int at) { - if (!at) + if (!at) { return; - if (unlikely(at > _l)) + } + if (unlikely(at > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } ::memmove(_b,_b + at,_l -= at); } @@ -375,8 +398,9 @@ public: inline void erase(const unsigned int at,const unsigned int length) { const unsigned int endr = at + length; - if (unlikely(endr > _l)) + if (unlikely(endr > _l)) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } ::memmove(_b + at,_b + endr,_l - endr); _l -= length; } diff --git a/node/C25519.cpp b/node/C25519.cpp index aec34835e..f425540d8 100644 --- a/node/C25519.cpp +++ b/node/C25519.cpp @@ -731,7 +731,9 @@ static void crypto_scalarmult(u8 *mypublic, const u8 *secret, const u8 *basepoin uint8_t e[32]; int i; - for (i = 0; i < 32; ++i) e[i] = secret[i]; + for (i = 0; i < 32; ++i) { + e[i] = secret[i]; + } e[0] &= 248; e[31] &= 127; e[31] |= 64; @@ -837,14 +839,12 @@ static inline void reduce_add_sub(fe25519 *r) crypto_uint32 t; int i,rep; - for(rep=0;rep<4;rep++) - { + for(rep=0;rep<4;rep++) { t = r->v[31] >> 7; r->v[31] &= 127; t = times19(t); r->v[0] += t; - for(i=0;i<31;i++) - { + for(i=0;i<31;i++) { t = r->v[i] >> 8; r->v[i+1] += t; r->v[i] &= 255; @@ -857,14 +857,12 @@ static inline void reduce_mul(fe25519 *r) crypto_uint32 t; int i,rep; - for(rep=0;rep<2;rep++) - { + for(rep=0;rep<2;rep++) { t = r->v[31] >> 7; r->v[31] &= 127; t = times19(t); r->v[0] += t; - for(i=0;i<31;i++) - { + for(i=0;i<31;i++) { t = r->v[i] >> 8; r->v[i+1] += t; r->v[i] &= 255; @@ -877,22 +875,26 @@ static inline void fe25519_freeze(fe25519 *r) { int i; crypto_uint32 m = equal(r->v[31],127); - for(i=30;i>0;i--) + for(i=30;i>0;i--) { m &= equal(r->v[i],255); + } m &= ge(r->v[0],237); m = -m; r->v[31] -= m&127; - for(i=30;i>0;i--) + for(i=30;i>0;i--) { r->v[i] -= m&255; + } r->v[0] -= m&237; } static inline void fe25519_unpack(fe25519 *r, const unsigned char x[32]) { int i; - for(i=0;i<32;i++) r->v[i] = x[i]; + for(i=0;i<32;i++) { + r->v[i] = x[i]; + } r->v[31] &= 127; } @@ -902,8 +904,9 @@ static inline void fe25519_pack(unsigned char r[32], const fe25519 *x) int i; fe25519 y = *x; fe25519_freeze(&y); - for(i=0;i<32;i++) + for(i=0;i<32;i++) { r[i] = y.v[i]; + } } static inline int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y) @@ -913,8 +916,11 @@ static inline int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y) fe25519 t2 = *y; fe25519_freeze(&t1); fe25519_freeze(&t2); - for(i=0;i<32;i++) - if(t1.v[i] != t2.v[i]) return 0; + for(i=0;i<32;i++) { + if (t1.v[i] != t2.v[i]) { + return 0; + } + } return 1; } @@ -923,7 +929,9 @@ static inline void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b) int i; crypto_uint32 mask = b; mask = -mask; - for(i=0;i<32;i++) r->v[i] ^= mask & (x->v[i] ^ r->v[i]); + for(i=0;i<32;i++) { + r->v[i] ^= mask & (x->v[i] ^ r->v[i]); + } } static inline unsigned char fe25519_getparity(const fe25519 *x) @@ -937,20 +945,26 @@ static inline void fe25519_setone(fe25519 *r) { int i; r->v[0] = 1; - for(i=1;i<32;i++) r->v[i]=0; + for(i=1;i<32;i++) { + r->v[i]=0; + } } static inline void fe25519_setzero(fe25519 *r) { int i; - for(i=0;i<32;i++) r->v[i]=0; + for(i=0;i<32;i++) { + r->v[i]=0; + } } static inline void fe25519_neg(fe25519 *r, const fe25519 *x) { fe25519 t; int i; - for(i=0;i<32;i++) t.v[i]=x->v[i]; + for(i=0;i<32;i++) { + t.v[i]=x->v[i]; + } fe25519_setzero(r); fe25519_sub(r, r, &t); } @@ -958,7 +972,9 @@ static inline void fe25519_neg(fe25519 *r, const fe25519 *x) static inline void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y) { int i; - for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i]; + for(i=0;i<32;i++) { + r->v[i] = x->v[i] + y->v[i]; + } reduce_add_sub(r); } @@ -968,8 +984,12 @@ static inline void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y) crypto_uint32 t[32]; t[0] = x->v[0] + 0x1da; t[31] = x->v[31] + 0xfe; - for(i=1;i<31;i++) t[i] = x->v[i] + 0x1fe; - for(i=0;i<32;i++) r->v[i] = t[i] - y->v[i]; + for(i=1;i<31;i++) { + t[i] = x->v[i] + 0x1fe; + } + for(i=0;i<32;i++) { + r->v[i] = t[i] - y->v[i]; + } reduce_add_sub(r); } @@ -977,14 +997,19 @@ static inline void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y) { int i,j; crypto_uint32 t[63]; - for(i=0;i<63;i++)t[i] = 0; + for(i=0;i<63;i++) { + t[i] = 0; + } - for(i=0;i<32;i++) - for(j=0;j<32;j++) + for(i=0;i<32;i++) { + for(j=0;j<32;j++) { t[i+j] += x->v[i] * y->v[j]; + } + } - for(i=32;i<63;i++) + for(i=32;i<63;i++) { r->v[i-32] = t[i-32] + times38(t[i]); + } r->v[31] = t[31]; /* result now in r[0]...r[31] */ reduce_mul(r); @@ -1136,16 +1161,16 @@ static inline void reduce_add_sub(sc25519 *r) int i; unsigned char t[32]; - for(i=0;i<32;i++) - { + for(i=0;i<32;i++) { pb += m[i]; b = lt(r->v[i],pb); t[i] = r->v[i]-pb+(b<<8); pb = b; } mask = b - 1; - for(i=0;i<32;i++) + for(i=0;i<32;i++) { r->v[i] ^= mask & (r->v[i] ^ t[i]); + } } /* Reduce coefficients of x before calling barrett_reduce */ @@ -1161,31 +1186,43 @@ static inline void barrett_reduce(sc25519 *r, const crypto_uint32 x[64]) crypto_uint32 pb = 0; crypto_uint32 b; - for (i = 0;i < 66;++i) q2[i] = 0; - for (i = 0;i < 33;++i) r2[i] = 0; + for (i = 0;i < 66;++i) { + q2[i] = 0; + } + for (i = 0;i < 33;++i) { + r2[i] = 0; + } - for(i=0;i<33;i++) - for(j=0;j<33;j++) - if(i+j >= 31) q2[i+j] += mu[i]*x[j+31]; + for(i=0;i<33;i++) { + for(j=0;j<33;j++) { + if(i+j >= 31) { + q2[i+j] += mu[i]*x[j+31]; + } + } + } carry = q2[31] >> 8; q2[32] += carry; carry = q2[32] >> 8; q2[33] += carry; - for(i=0;i<33;i++)r1[i] = x[i]; - for(i=0;i<32;i++) - for(j=0;j<33;j++) - if(i+j < 33) r2[i+j] += m[i]*q3[j]; + for(i=0;i<33;i++) { + r1[i] = x[i]; + } + for(i=0;i<32;i++) { + for(j=0;j<33;j++) { + if(i+j < 33) { + r2[i+j] += m[i]*q3[j]; + } + } + } - for(i=0;i<32;i++) - { + for(i=0;i<32;i++) { carry = r2[i] >> 8; r2[i+1] += carry; r2[i] &= 0xff; } - for(i=0;i<32;i++) - { + for(i=0;i<32;i++) { pb += r2[i]; b = lt(r1[i],pb); r->v[i] = r1[i]-pb+(b<<8); @@ -1204,8 +1241,12 @@ static inline void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]) { int i; crypto_uint32 t[64]; - for(i=0;i<32;i++) t[i] = x[i]; - for(i=32;i<64;++i) t[i] = 0; + for(i=0;i<32;i++) { + t[i] = x[i]; + } + for(i=32;i<64;++i) { + t[i] = 0; + } barrett_reduce(r, t); } @@ -1213,22 +1254,27 @@ static inline void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]) { int i; crypto_uint32 t[64]; - for(i=0;i<64;i++) t[i] = x[i]; + for(i=0;i<64;i++) { + t[i] = x[i]; + } barrett_reduce(r, t); } static inline void sc25519_to32bytes(unsigned char r[32], const sc25519 *x) { int i; - for(i=0;i<32;i++) r[i] = x->v[i]; + for(i=0;i<32;i++) { + r[i] = x->v[i]; + } } static inline void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y) { int i, carry; - for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i]; - for(i=0;i<31;i++) - { + for(i=0;i<32;i++) { + r->v[i] = x->v[i] + y->v[i]; + } + for(i=0;i<31;i++) { carry = r->v[i] >> 8; r->v[i+1] += carry; r->v[i] &= 0xff; @@ -1240,14 +1286,17 @@ static inline void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y) { int i,j,carry; crypto_uint32 t[64]; - for(i=0;i<64;i++)t[i] = 0; + for(i=0;i<64;i++) { + t[i] = 0; + } - for(i=0;i<32;i++) - for(j=0;j<32;j++) + for(i=0;i<32;i++) { + for(j=0;j<32;j++) { t[i+j] += x->v[i] * y->v[j]; + } + } - for(i=0;i<63;i++) - { + for(i=0;i<63;i++) { carry = t[i] >> 8; t[i+1] += carry; t[i] &= 0xff; @@ -1260,8 +1309,7 @@ static inline void sc25519_window3(signed char r[85], const sc25519 *s) { char carry; int i; - for(i=0;i<10;i++) - { + for(i=0;i<10;i++) { r[8*i+0] = s->v[3*i+0] & 7; r[8*i+1] = (s->v[3*i+0] >> 3) & 7; r[8*i+2] = (s->v[3*i+0] >> 6) & 7; @@ -1282,8 +1330,7 @@ static inline void sc25519_window3(signed char r[85], const sc25519 *s) /* Making it signed */ carry = 0; - for(i=0;i<84;i++) - { + for(i=0;i<84;i++) { r[i] += carry; r[i+1] += r[i] >> 3; r[i] &= 7; @@ -1296,8 +1343,7 @@ static inline void sc25519_window3(signed char r[85], const sc25519 *s) static inline void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2) { int i; - for(i=0;i<31;i++) - { + for(i=0;i<31;i++) { r[4*i] = ( s1->v[i] & 3) ^ (( s2->v[i] & 3) << 2); r[4*i+1] = ((s1->v[i] >> 2) & 3) ^ (((s2->v[i] >> 2) & 3) << 2); r[4*i+2] = ((s1->v[i] >> 4) & 3) ^ (((s2->v[i] >> 4) & 3) << 2); @@ -2341,18 +2387,21 @@ static inline int ge25519_unpackneg_vartime(ge25519_p3 *r, const unsigned char p /* 3. Check whether sqrt computation gave correct result, multiply by sqrt(-1) if not: */ fe25519_square(&chk, &r->x); fe25519_mul(&chk, &chk, &den); - if (!fe25519_iseq_vartime(&chk, &num)) + if (!fe25519_iseq_vartime(&chk, &num)) { fe25519_mul(&r->x, &r->x, &ge25519_sqrtm1); + } /* 4. Now we have one of the two square roots, except if input was not a square */ fe25519_square(&chk, &r->x); fe25519_mul(&chk, &chk, &den); - if (!fe25519_iseq_vartime(&chk, &num)) + if (!fe25519_iseq_vartime(&chk, &num)) { return -1; + } /* 5. Choose the desired square root according to parity: */ - if(fe25519_getparity(&r->x) != (1-par)) + if(fe25519_getparity(&r->x) != (1-par)) { fe25519_neg(&r->x, &r->x); + } fe25519_mul(&r->t, &r->x, &r->y); return 0; @@ -2399,18 +2448,19 @@ static inline void ge25519_double_scalarmult_vartime(ge25519_p3 *r, const ge2551 /* scalar multiplication */ *r = pre[b[126]]; - for(i=125;i>=0;i--) - { + for(i=125;i>=0;i--) { dbl_p1p1(&tp1p1, (ge25519_p2 *)r); p1p1_to_p2((ge25519_p2 *) r, &tp1p1); dbl_p1p1(&tp1p1, (ge25519_p2 *)r); - if(b[i]!=0) - { + if(b[i]!=0) { p1p1_to_p3(r, &tp1p1); add_p1p1(&tp1p1, r, &pre[b[i]]); } - if(i != 0) p1p1_to_p2((ge25519_p2 *)r, &tp1p1); - else p1p1_to_p3(r, &tp1p1); + if (i != 0) { + p1p1_to_p2((ge25519_p2 *)r, &tp1p1); + } else { + p1p1_to_p3(r, &tp1p1); + } } } @@ -2424,8 +2474,7 @@ static inline void ge25519_scalarmult_base(ge25519_p3 *r, const sc25519 *s) choose_t((ge25519_aff *)r, 0, b[0]); fe25519_setone(&r->z); fe25519_mul(&r->t, &r->x, &r->y); - for(i=1;i<85;i++) - { + for(i=1;i<85;i++) { choose_t(&t, (unsigned long long) i, b[i]); ge25519_mixadd2(r, &t); } @@ -2435,9 +2484,15 @@ static inline void get_hram(unsigned char *hram, const unsigned char *sm, const { unsigned long long i; - for (i = 0;i < 32;++i) playground[i] = sm[i]; - for (i = 32;i < 64;++i) playground[i] = pk[i-32]; - for (i = 64;i < smlen;++i) playground[i] = sm[i]; + for (i = 0;i < 32;++i) { + playground[i] = sm[i]; + } + for (i = 32;i < 64;++i) { + playground[i] = pk[i-32]; + } + for (i = 64;i < smlen;++i) { + playground[i] = sm[i]; + } ZeroTier::SHA512(hram,playground,(unsigned int)smlen); } @@ -2491,10 +2546,12 @@ void C25519::sign(const C25519::Private &myPrivate,const C25519::Public &myPubli extsk[31] &= 127; extsk[31] |= 64; - for(unsigned int i=0;i<32;i++) + for(unsigned int i=0;i<32;i++) { sig[32 + i] = extsk[32 + i]; - for(unsigned int i=0;i<32;i++) + } + for(unsigned int i=0;i<32;i++) { sig[64 + i] = digest[i]; + } SHA512(hmg,sig + 32,64); @@ -2504,8 +2561,9 @@ void C25519::sign(const C25519::Private &myPrivate,const C25519::Public &myPubli ge25519_pack(r, &ger); /* Computation of s */ - for(unsigned int i=0;i<32;i++) + for(unsigned int i=0;i<32;i++) { sig[i] = r[i]; + } get_hram(hram,sig,myPublic.data + 32,sig,96); @@ -2516,8 +2574,9 @@ void C25519::sign(const C25519::Private &myPrivate,const C25519::Public &myPubli sc25519_add(&scs, &scs, &sck); sc25519_to32bytes(s,&scs); /* cat s */ - for(unsigned int i=0;i<32;i++) + for(unsigned int i=0;i<32;i++) { sig[32 + i] = s[i]; + } #endif } @@ -2526,8 +2585,9 @@ bool C25519::verify(const C25519::Public &their,const void *msg,unsigned int len const unsigned char *const sig = (const unsigned char *)signature; unsigned char digest[64]; // we sign the first 32 bytes of SHA-512(msg) SHA512(digest,msg,len); - if (!Utils::secureEq(sig + 64,digest,32)) + if (!Utils::secureEq(sig + 64,digest,32)) { return false; + } unsigned char t2[32]; ge25519 get1, get2; @@ -2535,8 +2595,9 @@ bool C25519::verify(const C25519::Public &their,const void *msg,unsigned int len unsigned char hram[crypto_hash_sha512_BYTES]; unsigned char m[96]; - if (ge25519_unpackneg_vartime(&get1,their.data + 32)) + if (ge25519_unpackneg_vartime(&get1,their.data + 32)) { return false; + } get_hram(hram,sig,their.data + 32,m,96); diff --git a/node/Capability.cpp b/node/Capability.cpp index 21e7144f8..d4350ebf7 100644 --- a/node/Capability.cpp +++ b/node/Capability.cpp @@ -25,27 +25,31 @@ int Capability::verify(const RuntimeEnvironment *RR,void *tPtr) const { try { // There must be at least one entry, and sanity check for bad chain max length - if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) { return -1; + } // Validate all entries in chain of custody Buffer<(sizeof(Capability) * 2)> tmp; this->serialize(tmp,true); for(unsigned int c=0;c<_maxCustodyChainLength;++c) { if (c == 0) { - if ((!_custody[c].to)||(!_custody[c].from)||(_custody[c].from != Network::controllerFor(_nwid))) + if ((!_custody[c].to)||(!_custody[c].from)||(_custody[c].from != Network::controllerFor(_nwid))) { return -1; // the first entry must be present and from the network's controller + } } else { - if (!_custody[c].to) + if (!_custody[c].to) { return 0; // all previous entries were valid, so we are valid - else if ((!_custody[c].from)||(_custody[c].from != _custody[c-1].to)) + } else if ((!_custody[c].from)||(_custody[c].from != _custody[c-1].to)) { return -1; // otherwise if we have another entry it must be from the previous holder in the chain + } } const Identity id(RR->topology->getIdentity(tPtr,_custody[c].from)); if (id) { - if (!id.verify(tmp.data(),tmp.size(),_custody[c].signature)) + if (!id.verify(tmp.data(),tmp.size(),_custody[c].signature)) { return -1; + } } else { RR->sw->requestWhois(tPtr,RR->node->now(),_custody[c].from); return 1; diff --git a/node/Capability.hpp b/node/Capability.hpp index 115973d50..9e2409b93 100644 --- a/node/Capability.hpp +++ b/node/Capability.hpp @@ -85,8 +85,9 @@ public: _maxCustodyChainLength((mccl > 0) ? ((mccl < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) ? mccl : (unsigned int)ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) : 1), _ruleCount((ruleCount < ZT_MAX_CAPABILITY_RULES) ? ruleCount : ZT_MAX_CAPABILITY_RULES) { - if (_ruleCount > 0) + if (_ruleCount > 0) { memcpy(_rules,rules,sizeof(ZT_VirtualNetworkRule) * _ruleCount); + } } /** @@ -121,9 +122,11 @@ public: { Address i2; for(unsigned int i=0;i inline void serialize(Buffer &b,const bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } // These are the same between Tag and Capability b.append(_nwid); @@ -409,7 +414,9 @@ public: // This is the size of any additional fields, currently 0. b.append((uint16_t)0); - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } } template @@ -419,40 +426,53 @@ public: unsigned int p = startAt; - _nwid = b.template at(p); p += 8; - _ts = b.template at(p); p += 8; - _id = b.template at(p); p += 4; + _nwid = b.template at(p); + p += 8; + _ts = b.template at(p); + p += 8; + _id = b.template at(p); + p += 4; - const unsigned int rc = b.template at(p); p += 2; - if (rc > ZT_MAX_CAPABILITY_RULES) + const unsigned int rc = b.template at(p); + p += 2; + if (rc > ZT_MAX_CAPABILITY_RULES) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } deserializeRules(b,p,_rules,_ruleCount,rc); _maxCustodyChainLength = (unsigned int)b[p++]; - if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } for(unsigned int i=0;;++i) { - const Address to(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - if (!to) + const Address to(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + if (!to) { break; - if ((i >= _maxCustodyChainLength)||(i >= ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + } + if ((i >= _maxCustodyChainLength)||(i >= ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } _custody[i].to = to; - _custody[i].from.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _custody[i].from.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; if (b[p++] == 1) { - if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } p += 2; - memcpy(_custody[i].signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + memcpy(_custody[i].signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; } else { p += 2 + b.template at(p); } } p += 2 + b.template at(p); - if (p > b.size()) + if (p > b.size()) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } return (p - startAt); } diff --git a/node/CertificateOfMembership.cpp b/node/CertificateOfMembership.cpp index dbda9939f..94effd028 100644 --- a/node/CertificateOfMembership.cpp +++ b/node/CertificateOfMembership.cpp @@ -48,25 +48,30 @@ CertificateOfMembership::CertificateOfMembership(uint64_t timestamp,uint64_t tim bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other, const Identity &otherIdentity) const { - if ((_qualifierCount == 0)||(other._qualifierCount == 0)) + if ((_qualifierCount == 0)||(other._qualifierCount == 0)) { return false; + } std::map< uint64_t, uint64_t > otherFields; - for(unsigned int i=0;i= 3)&&(qid <= 6)) + if ((qid >= 3)&&(qid <= 6)) { fullIdentityVerification = true; + } std::map< uint64_t, uint64_t >::iterator otherQ(otherFields.find(qid)); - if (otherQ == otherFields.end()) + if (otherQ == otherFields.end()) { return false; + } const uint64_t a = _qualifiers[i].value; const uint64_t b = otherQ->second; - if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[i].maxDelta) + if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[i].maxDelta) { return false; + } } // If this COM has a full hash of its identity, assume the other must have this as well. @@ -76,10 +81,12 @@ bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other, c otherIdentity.publicKeyHash(idHash); for(unsigned long i=0;i<4;++i) { std::map< uint64_t, uint64_t >::iterator otherQ(otherFields.find((uint64_t)(i + 3))); - if (otherQ == otherFields.end()) + if (otherQ == otherFields.end()) { return false; - if (otherQ->second != Utils::ntoh(idHash[i])) + } + if (otherQ->second != Utils::ntoh(idHash[i])) { return false; + } } } @@ -108,8 +115,9 @@ bool CertificateOfMembership::sign(const Identity &with) int CertificateOfMembership::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if ((!_signedBy)||(_signedBy != Network::controllerFor(networkId()))||(_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) + if ((!_signedBy)||(_signedBy != Network::controllerFor(networkId()))||(_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) { return -1; + } const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); if (!id) { diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp index 1948dd7b7..eadfbb090 100644 --- a/node/CertificateOfMembership.hpp +++ b/node/CertificateOfMembership.hpp @@ -142,8 +142,9 @@ public: inline int64_t timestamp() const { for(unsigned int i=0;i<_qualifierCount;++i) { - if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) + if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) { return _qualifiers[i].value; + } } return 0; } @@ -154,8 +155,9 @@ public: inline Address issuedTo() const { for(unsigned int i=0;i<_qualifierCount;++i) { - if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO) + if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO) { return Address(_qualifiers[i].value); + } } return Address(); } @@ -166,8 +168,9 @@ public: inline uint64_t networkId() const { for(unsigned int i=0;i<_qualifierCount;++i) { - if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID) + if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID) { return _qualifiers[i].value; + } } return 0ULL; } @@ -226,8 +229,9 @@ public: b.append(_qualifiers[i].maxDelta); } _signedBy.appendTo(b); - if (_signedBy) + if (_signedBy) { b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } } template @@ -238,16 +242,20 @@ public: _qualifierCount = 0; _signedBy.zero(); - if (b[p++] != 1) + if (b[p++] != 1) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE; + } - unsigned int numq = b.template at(p); p += sizeof(uint16_t); + unsigned int numq = b.template at(p); + p += sizeof(uint16_t); uint64_t lastId = 0; for(unsigned int i=0;i(p); - if (qid < lastId) + if (qid < lastId) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING; - else lastId = qid; + } else { + lastId = qid; + } if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) { _qualifiers[_qualifierCount].id = qid; _qualifiers[_qualifierCount].value = b.template at(p + 8); @@ -272,15 +280,18 @@ public: inline bool operator==(const CertificateOfMembership &c) const { - if (_signedBy != c._signedBy) + if (_signedBy != c._signedBy) { return false; - if (_qualifierCount != c._qualifierCount) + } + if (_qualifierCount != c._qualifierCount) { return false; + } for(unsigned int i=0;i<_qualifierCount;++i) { const _Qualifier &a = _qualifiers[i]; const _Qualifier &b = c._qualifiers[i]; - if ((a.id != b.id)||(a.value != b.value)||(a.maxDelta != b.maxDelta)) + if ((a.id != b.id)||(a.value != b.value)||(a.maxDelta != b.maxDelta)) { return false; + } } return (memcmp(_signature.data,c._signature.data,ZT_C25519_SIGNATURE_LEN) == 0); } diff --git a/node/CertificateOfOwnership.cpp b/node/CertificateOfOwnership.cpp index 70be0a044..11be4fe91 100644 --- a/node/CertificateOfOwnership.cpp +++ b/node/CertificateOfOwnership.cpp @@ -23,8 +23,9 @@ namespace ZeroTier { int CertificateOfOwnership::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) { return -1; + } const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); if (!id) { RR->sw->requestWhois(tPtr,RR->node->now(),_signedBy); @@ -45,12 +46,14 @@ bool CertificateOfOwnership::_owns(const CertificateOfOwnership::Thing &t,const if (_thingTypes[i] == (uint8_t)t) { unsigned int k = 0; while (k < l) { - if (reinterpret_cast(v)[k] != _thingValues[i][k]) + if (reinterpret_cast(v)[k] != _thingValues[i][k]) { break; + } ++k; } - if (k == l) + if (k == l) { return true; + } } } return false; diff --git a/node/CertificateOfOwnership.hpp b/node/CertificateOfOwnership.hpp index abd62df1a..e7d549fdc 100644 --- a/node/CertificateOfOwnership.hpp +++ b/node/CertificateOfOwnership.hpp @@ -80,10 +80,12 @@ public: inline bool owns(const InetAddress &ip) const { - if (ip.ss_family == AF_INET) + if (ip.ss_family == AF_INET) { return this->_owns(THING_IPV4_ADDRESS,&(reinterpret_cast(&ip)->sin_addr.s_addr),4); - if (ip.ss_family == AF_INET6) + } + if (ip.ss_family == AF_INET6) { return this->_owns(THING_IPV6_ADDRESS,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + } return false; } @@ -96,7 +98,9 @@ public: inline void addThing(const InetAddress &ip) { - if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return; + if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) { + return; + } if (ip.ss_family == AF_INET) { _thingTypes[_thingCount] = THING_IPV4_ADDRESS; memcpy(_thingValues[_thingCount],&(reinterpret_cast(&ip)->sin_addr.s_addr),4); @@ -110,7 +114,9 @@ public: inline void addThing(const MAC &mac) { - if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return; + if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) { + return; + } _thingTypes[_thingCount] = THING_MAC_ADDRESS; mac.copyTo(_thingValues[_thingCount],6); ++_thingCount; @@ -142,7 +148,9 @@ public: template inline void serialize(Buffer &b,const bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } b.append(_networkId); b.append(_ts); @@ -164,7 +172,9 @@ public: b.append((uint16_t)0); // length of additional fields, currently 0 - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } } template @@ -174,11 +184,16 @@ public: *this = CertificateOfOwnership(); - _networkId = b.template at(p); p += 8; - _ts = b.template at(p); p += 8; - _flags = b.template at(p); p += 8; - _id = b.template at(p); p += 4; - _thingCount = b.template at(p); p += 2; + _networkId = b.template at(p); + p += 8; + _ts = b.template at(p); + p += 8; + _flags = b.template at(p); + p += 8; + _id = b.template at(p); + p += 4; + _thingCount = b.template at(p); + p += 2; for(unsigned int i=0,j=_thingCount;i(p) != ZT_C25519_SIGNATURE_LEN) + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } p += 2; - memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; } else { p += 2 + b.template at(p); } p += 2 + b.template at(p); - if (p > b.size()) + if (p > b.size()) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } return (p - startAt); } diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp index 7a4572427..c6a3c7bea 100644 --- a/node/Dictionary.hpp +++ b/node/Dictionary.hpp @@ -55,10 +55,14 @@ public: { for(unsigned int i=0;iget(key,tmp,sizeof(tmp)) >= 0) + if (this->get(key,tmp,sizeof(tmp)) >= 0) { return ((*tmp == '1')||(*tmp == 't')||(*tmp == 'T')); + } return dfl; } @@ -257,8 +280,9 @@ public: inline uint64_t getUI(const char *key,uint64_t dfl = 0) const { char tmp[128]; - if (this->get(key,tmp,sizeof(tmp)) >= 1) + if (this->get(key,tmp,sizeof(tmp)) >= 1) { return Utils::hexStrToU64(tmp); + } return dfl; } @@ -272,8 +296,9 @@ public: inline int64_t getI(const char *key,int64_t dfl = 0) const { char tmp[128]; - if (this->get(key,tmp,sizeof(tmp)) >= 1) + if (this->get(key,tmp,sizeof(tmp)) >= 1) { return Utils::hexStrTo64(tmp); + } return dfl; } @@ -335,11 +360,21 @@ public: return false; } switch(*p) { - case 0: _d[j++] = '0'; break; - case 13: _d[j++] = 'r'; break; - case 10: _d[j++] = 'n'; break; - case '\\': _d[j++] = '\\'; break; - case '=': _d[j++] = 'e'; break; + case 0: + _d[j++] = '0'; + break; + case 13: + _d[j++] = 'r'; + break; + case 10: + _d[j++] = 'n'; + break; + case '\\': + _d[j++] = '\\'; + break; + case '=': + _d[j++] = 'e'; + break; } if (j == C) { _d[i] = (char)0; diff --git a/node/Hashtable.hpp b/node/Hashtable.hpp index 5a38f29cf..5fc6e0bb7 100644 --- a/node/Hashtable.hpp +++ b/node/Hashtable.hpp @@ -81,8 +81,9 @@ public: return true; } ++_idx; - if (_idx >= _ht->_bc) + if (_idx >= _ht->_bc) { return false; + } _b = _ht->_t[_idx]; } } @@ -102,10 +103,12 @@ public: _bc(bc), _s(0) { - if (!_t) + if (!_t) { throw ZT_EXCEPTION_OUT_OF_MEMORY; - for(unsigned long i=0;i &ht) : @@ -113,10 +116,12 @@ public: _bc(ht._bc), _s(ht._s) { - if (!_t) + if (!_t) { throw ZT_EXCEPTION_OUT_OF_MEMORY; - for(unsigned long i=0;i<_bc;++i) + } + for(unsigned long i=0;i<_bc;++i) { _t[i] = (_Bucket *)0; + } for(unsigned long i=0;i<_bc;++i) { const _Bucket *b = ht._t[i]; while (b) { @@ -234,8 +239,9 @@ public: { _Bucket *b = _t[_hc(k) % _bc]; while (b) { - if (b->k == k) + if (b->k == k) { return &(b->v); + } b = b->next; } return (V *)0; @@ -268,8 +274,9 @@ public: { _Bucket *b = _t[_hc(k) % _bc]; while (b) { - if (b->k == k) + if (b->k == k) { return true; + } b = b->next; } return false; @@ -286,9 +293,11 @@ public: _Bucket *b = _t[bidx]; while (b) { if (b->k == k) { - if (lastb) + if (lastb) { lastb->next = b->next; - else _t[bidx] = b->next; + } else { + _t[bidx] = b->next; + } delete b; --_s; return true; @@ -341,8 +350,9 @@ public: _Bucket *b = _t[bidx]; while (b) { - if (b->k == k) + if (b->k == k) { return b->v; + } b = b->next; } @@ -396,8 +406,9 @@ private: const unsigned long nc = _bc * 2; _Bucket **nt = reinterpret_cast<_Bucket **>(::malloc(sizeof(_Bucket *) * nc)); if (nt) { - for(unsigned long i=0;i @@ -80,8 +81,9 @@ public: _address = id._address; _publicKey = id._publicKey; if (id._privateKey) { - if (!_privateKey) + if (!_privateKey) { _privateKey = new C25519::Private(); + } *_privateKey = *(id._privateKey); } else { delete _privateKey; @@ -144,8 +146,9 @@ public: */ inline C25519::Signature sign(const void *data,unsigned int len) const { - if (_privateKey) + if (_privateKey) { return C25519::sign(*_privateKey,_publicKey,data,len); + } throw ZT_EXCEPTION_PRIVATE_KEY_REQUIRED; } @@ -160,8 +163,9 @@ public: */ inline bool verify(const void *data,unsigned int len,const void *signature,unsigned int siglen) const { - if (siglen != ZT_C25519_SIGNATURE_LEN) + if (siglen != ZT_C25519_SIGNATURE_LEN) { return false; + } return C25519::verify(_publicKey,data,len,signature); } @@ -217,7 +221,9 @@ public: if ((_privateKey)&&(includePrivate)) { b.append((unsigned char)ZT_C25519_PRIVATE_KEY_LEN); b.append(_privateKey->data,ZT_C25519_PRIVATE_KEY_LEN); - } else b.append((unsigned char)0); + } else { + b.append((unsigned char)0); + } } /** @@ -243,16 +249,18 @@ public: _address.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - if (b[p++] != 0) + if (b[p++] != 0) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE; + } memcpy(_publicKey.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); p += ZT_C25519_PUBLIC_KEY_LEN; unsigned int privateKeyLength = (unsigned int)b[p++]; if (privateKeyLength) { - if (privateKeyLength != ZT_C25519_PRIVATE_KEY_LEN) + if (privateKeyLength != ZT_C25519_PRIVATE_KEY_LEN) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } _privateKey = new C25519::Private(); memcpy(_privateKey->data,b.field(p,ZT_C25519_PRIVATE_KEY_LEN),ZT_C25519_PRIVATE_KEY_LEN); p += ZT_C25519_PRIVATE_KEY_LEN; @@ -293,9 +301,11 @@ public: { C25519::Pair pair; pair.pub = _publicKey; - if (_privateKey) + if (_privateKey) { pair.priv = *_privateKey; - else memset(pair.priv.data,0,ZT_C25519_PRIVATE_KEY_LEN); + } else { + memset(pair.priv.data,0,ZT_C25519_PRIVATE_KEY_LEN); + } return pair; } diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 12534117f..f748bdb35 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -86,28 +86,69 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t f switch(v) { //case Packet::VERB_NOP: default: // ignore unknown verbs, but if they pass auth check they are "received" + Metrics::pkt_nop_in++; peer->received(tPtr,_path,hops(),packetId(),payloadLength(),v,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); break; - case Packet::VERB_HELLO: r = _doHELLO(RR,tPtr,true); break; - case Packet::VERB_ACK : r = _doACK(RR,tPtr,peer); break; - case Packet::VERB_QOS_MEASUREMENT: r = _doQOS_MEASUREMENT(RR,tPtr,peer); break; - case Packet::VERB_ERROR: r = _doERROR(RR,tPtr,peer); break; - case Packet::VERB_OK: r = _doOK(RR,tPtr,peer); break; - case Packet::VERB_WHOIS: r = _doWHOIS(RR,tPtr,peer); break; - case Packet::VERB_RENDEZVOUS: r = _doRENDEZVOUS(RR,tPtr,peer); break; - case Packet::VERB_FRAME: r = _doFRAME(RR,tPtr,peer,flowId); break; - case Packet::VERB_EXT_FRAME: r = _doEXT_FRAME(RR,tPtr,peer,flowId); break; - case Packet::VERB_ECHO: r = _doECHO(RR,tPtr,peer); break; - case Packet::VERB_MULTICAST_LIKE: r = _doMULTICAST_LIKE(RR,tPtr,peer); break; - case Packet::VERB_NETWORK_CREDENTIALS: r = _doNETWORK_CREDENTIALS(RR,tPtr,peer); break; - case Packet::VERB_NETWORK_CONFIG_REQUEST: r = _doNETWORK_CONFIG_REQUEST(RR,tPtr,peer); break; - case Packet::VERB_NETWORK_CONFIG: r = _doNETWORK_CONFIG(RR,tPtr,peer); break; - case Packet::VERB_MULTICAST_GATHER: r = _doMULTICAST_GATHER(RR,tPtr,peer); break; - case Packet::VERB_MULTICAST_FRAME: r = _doMULTICAST_FRAME(RR,tPtr,peer); break; - case Packet::VERB_PUSH_DIRECT_PATHS: r = _doPUSH_DIRECT_PATHS(RR,tPtr,peer); break; - case Packet::VERB_USER_MESSAGE: r = _doUSER_MESSAGE(RR,tPtr,peer); break; - case Packet::VERB_REMOTE_TRACE: r = _doREMOTE_TRACE(RR,tPtr,peer); break; - case Packet::VERB_PATH_NEGOTIATION_REQUEST: r = _doPATH_NEGOTIATION_REQUEST(RR,tPtr,peer); break; + case Packet::VERB_HELLO: + r = _doHELLO(RR, tPtr, true); + break; + case Packet::VERB_ACK: + r = _doACK(RR, tPtr, peer); + break; + case Packet::VERB_QOS_MEASUREMENT: + r = _doQOS_MEASUREMENT(RR, tPtr, peer); + break; + case Packet::VERB_ERROR: + r = _doERROR(RR, tPtr, peer); + break; + case Packet::VERB_OK: + r = _doOK(RR, tPtr, peer); + break; + case Packet::VERB_WHOIS: + r = _doWHOIS(RR, tPtr, peer); + break; + case Packet::VERB_RENDEZVOUS: + r = _doRENDEZVOUS(RR, tPtr, peer); + break; + case Packet::VERB_FRAME: + r = _doFRAME(RR, tPtr, peer, flowId); + break; + case Packet::VERB_EXT_FRAME: + r = _doEXT_FRAME(RR, tPtr, peer, flowId); + break; + case Packet::VERB_ECHO: + r = _doECHO(RR, tPtr, peer); + break; + case Packet::VERB_MULTICAST_LIKE: + r = _doMULTICAST_LIKE(RR, tPtr, peer); + break; + case Packet::VERB_NETWORK_CREDENTIALS: + r = _doNETWORK_CREDENTIALS(RR, tPtr, peer); + break; + case Packet::VERB_NETWORK_CONFIG_REQUEST: + r = _doNETWORK_CONFIG_REQUEST(RR, tPtr, peer); + break; + case Packet::VERB_NETWORK_CONFIG: + r = _doNETWORK_CONFIG(RR, tPtr, peer); + break; + case Packet::VERB_MULTICAST_GATHER: + r = _doMULTICAST_GATHER(RR, tPtr, peer); + break; + case Packet::VERB_MULTICAST_FRAME: + r = _doMULTICAST_FRAME(RR, tPtr, peer); + break; + case Packet::VERB_PUSH_DIRECT_PATHS: + r = _doPUSH_DIRECT_PATHS(RR, tPtr, peer); + break; + case Packet::VERB_USER_MESSAGE: + r = _doUSER_MESSAGE(RR, tPtr, peer); + break; + case Packet::VERB_REMOTE_TRACE: + r = _doREMOTE_TRACE(RR, tPtr, peer); + break; + case Packet::VERB_PATH_NEGOTIATION_REQUEST: + r = _doPATH_NEGOTIATION_REQUEST(RR, tPtr, peer); + break; } if (r) { RR->node->statsLogVerb((unsigned int)v,(unsigned int)size()); @@ -131,7 +172,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar const Packet::ErrorCode errorCode = (Packet::ErrorCode)(*this)[ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE]; uint64_t networkId = 0; - Metrics::pkt_error++; + Metrics::pkt_error_in++; /* Security note: we do not gate doERROR() with expectingReplyTo() to * avoid having to log every outgoing packet ID. Instead we put the @@ -145,10 +186,11 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar // Object not found, currently only meaningful from network controllers. if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); - if ((network)&&(network->controller() == peer->address())) + if ((network)&&(network->controller() == peer->address())) { network->setNotFound(tPtr); + } } - Metrics::pkt_error_obj_not_found++; + Metrics::pkt_error_obj_not_found_in++; break; case Packet::ERROR_UNSUPPORTED_OPERATION: @@ -157,10 +199,11 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar // that the queried node does not support acting as a controller. if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); - if ((network)&&(network->controller() == peer->address())) + if ((network)&&(network->controller() == peer->address())) { network->setNotFound(tPtr); + } } - Metrics::pkt_error_unsupported_op++; + Metrics::pkt_error_unsupported_op_in++; break; case Packet::ERROR_IDENTITY_COLLISION: @@ -168,7 +211,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar if (RR->topology->isUpstream(peer->identity())) { RR->node->postEvent(tPtr,ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION); } - Metrics::pkt_error_identity_collision++; + Metrics::pkt_error_identity_collision_in++; break; case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: { @@ -179,7 +222,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar if ((network)&&(network->config().com)) { network->peerRequestedCredentials(tPtr,peer->address(),now); } - Metrics::pkt_error_need_membership_cert++; + Metrics::pkt_error_need_membership_cert_in++; } break; case Packet::ERROR_NETWORK_ACCESS_DENIED_: { @@ -188,7 +231,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar if ((network)&&(network->controller() == peer->address())) { network->setAccessDenied(tPtr); } - Metrics::pkt_error_network_access_denied++; + Metrics::pkt_error_network_access_denied_in++; } break; case Packet::ERROR_UNWANTED_MULTICAST: { @@ -200,7 +243,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14)); RR->mc->remove(network->id(),mg,peer->address()); } - Metrics::pkt_error_unwanted_multicast++; + Metrics::pkt_error_unwanted_multicast_in++; } break; case Packet::ERROR_NETWORK_AUTHENTICATION_REQUIRED: { @@ -259,10 +302,11 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar network->setAuthenticationRequired(tPtr, ""); } } - Metrics::pkt_error_authentication_required++; + Metrics::pkt_error_authentication_required_in++; } break; - default: break; + default: + break; } peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_ERROR,inRePacketId,inReVerb,false,networkId,ZT_QOS_NO_FLOW); @@ -286,13 +330,13 @@ bool IncomingPacket::_doACK(const RuntimeEnvironment* RR, void* tPtr, const Shar bond->receivedAck(_path, RR->node->now(), Utils::ntoh(ackedBytes)); } */ - Metrics::pkt_ack++; + Metrics::pkt_ack_in++; return true; } bool IncomingPacket::_doQOS_MEASUREMENT(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr& peer) { - Metrics::pkt_qos++; + Metrics::pkt_qos_in++; SharedPtr bond = peer->bond(); if (! bond || ! bond->rateGateQoS(RR->node->now(), _path)) { return true; @@ -323,7 +367,7 @@ bool IncomingPacket::_doQOS_MEASUREMENT(const RuntimeEnvironment* RR, void* tPtr bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool alreadyAuthenticated) { - Metrics::pkt_hello++; + Metrics::pkt_hello_in++; const int64_t now = RR->node->now(); const uint64_t pid = packetId(); @@ -353,8 +397,9 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool // Identity is different from the one we already have -- address collision // Check rate limits - if (!RR->node->rateGateIdentityVerification(now,_path->address())) + if (!RR->node->rateGateIdentityVerification(now,_path->address())) { return true; + } uint8_t key[ZT_SYMMETRIC_KEY_SIZE]; if (RR->identity.agree(id,key)) { @@ -365,6 +410,8 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.append((uint64_t)pid); outp.append((uint8_t)Packet::ERROR_IDENTITY_COLLISION); outp.armor(key,true,peer->aesKeysIfSupported()); + Metrics::pkt_error_out++; + Metrics::pkt_error_identity_collision_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } else { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC"); @@ -424,16 +471,19 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool InetAddress externalSurfaceAddress; if (ptr < size()) { ptr += externalSurfaceAddress.deserialize(*this,ptr); - if ((externalSurfaceAddress)&&(hops() == 0)) + if ((externalSurfaceAddress)&&(hops() == 0)) { RR->sa->iam(tPtr,id.address(),_path->localSocket(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(id),now); + } } // Get primary planet world ID and world timestamp if present uint64_t planetWorldId = 0; uint64_t planetWorldTimestamp = 0; if ((ptr + 16) <= size()) { - planetWorldId = at(ptr); ptr += 8; - planetWorldTimestamp = at(ptr); ptr += 8; + planetWorldId = at(ptr); + ptr += 8; + planetWorldTimestamp = at(ptr); + ptr += 8; } std::vector< std::pair > moonIdsAndTimestamps; @@ -443,10 +493,12 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool // Get moon IDs and timestamps if present if ((ptr + 2) <= size()) { - const unsigned int numMoons = at(ptr); ptr += 2; + const unsigned int numMoons = at(ptr); + ptr += 2; for(unsigned int i=0;i(at(ptr),at(ptr + 8))); + } ptr += 16; } } @@ -505,8 +557,9 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { for(std::vector< std::pair >::const_iterator i(moonIdsAndTimestamps.begin());i!=moonIdsAndTimestamps.end();++i) { if (i->first == m->id()) { - if (m->timestamp() > i->second) + if (m->timestamp() > i->second) { m->serialize(outp,false); + } break; } } @@ -516,6 +569,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),now); peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version @@ -526,13 +580,14 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_ok++; + Metrics::pkt_ok_in++; const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB]; const uint64_t inRePacketId = at(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID); uint64_t networkId = 0; - if (!RR->node->expectingReplyTo(inRePacketId)) + if (!RR->node->expectingReplyTo(inRePacketId)) { return true; + } switch(inReVerb) { @@ -542,19 +597,22 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION]; const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION]; const unsigned int vRevision = at(ZT_PROTO_VERB_HELLO__OK__IDX_REVISION); - if (vProto < ZT_PROTO_VERSION_MIN) + if (vProto < ZT_PROTO_VERSION_MIN) { return true; + } InetAddress externalSurfaceAddress; unsigned int ptr = ZT_PROTO_VERB_HELLO__OK__IDX_REVISION + 2; // Get reported external surface address if present - if (ptr < size()) + if (ptr < size()) { ptr += externalSurfaceAddress.deserialize(*this,ptr); + } // Handle planet or moon updates if present if ((ptr + 2) <= size()) { - const unsigned int worldsLen = at(ptr); ptr += 2; + const unsigned int worldsLen = at(ptr); + ptr += 2; if (RR->topology->shouldAcceptWorldUpdateFrom(peer->address())) { const unsigned int endOfWorlds = ptr + worldsLen; while (ptr < endOfWorlds) { @@ -576,8 +634,9 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP peer->setRemoteVersion(vProto,vMajor,vMinor,vRevision); - if ((externalSurfaceAddress)&&(hops() == 0)) + if ((externalSurfaceAddress)&&(hops() == 0)) { RR->sa->iam(tPtr,peer->address(),_path->localSocket(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(peer->identity()),RR->node->now()); + } } break; case Packet::VERB_WHOIS: @@ -590,8 +649,9 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP case Packet::VERB_NETWORK_CONFIG_REQUEST: { networkId = at(ZT_PROTO_VERB_OK_IDX_PAYLOAD); const SharedPtr network(RR->node->network(networkId)); - if (network) + if (network) { network->handleConfigChunk(tPtr,packetId(),source(),*this,ZT_PROTO_VERB_OK_IDX_PAYLOAD); + } } break; case Packet::VERB_MULTICAST_GATHER: { @@ -616,21 +676,25 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP if ((flags & 0x01) != 0) { // deprecated but still used by older peers CertificateOfMembership com; offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS); - if (com) + if (com) { network->addCredential(tPtr,com); + } } if ((flags & 0x02) != 0) { // OK(MULTICAST_FRAME) includes implicit gather results offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS; - unsigned int totalKnown = at(offset); offset += 4; - unsigned int count = at(offset); offset += 2; + unsigned int totalKnown = at(offset); + offset += 4; + unsigned int count = at(offset); + offset += 2; RR->mc->addMultiple(tPtr,RR->node->now(),networkId,mg,field(offset,count * 5),count,totalKnown); } } } break; - default: break; + default: + break; } peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_OK,inRePacketId,inReVerb,false,networkId,ZT_QOS_NO_FLOW); @@ -644,7 +708,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar return true; } - Metrics::pkt_whois++; + Metrics::pkt_whois_in++; Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); outp.append((unsigned char)Packet::VERB_WHOIS); @@ -667,6 +731,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar } if (count > 0) { + Metrics::pkt_ok_out++; outp.armor(peer->key(),true,peer->aesKeysIfSupported()); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -678,7 +743,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_rendezvous++; + Metrics::pkt_rendezvous_in++; if (RR->topology->isUpstream(peer->identity())) { const Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); const SharedPtr rendezvousWith(RR->topology->getPeer(tPtr,with)); @@ -704,8 +769,9 @@ bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const // Returns true if packet appears valid; pos and proto will be set static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) { - if (frameLen < 40) + if (frameLen < 40) { return false; + } pos = 40; proto = frameData[6]; while (pos <= frameLen) { @@ -714,8 +780,9 @@ static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsig case 43: // routing case 60: // destination options case 135: // mobility options - if ((pos + 8) > frameLen) + if ((pos + 8) > frameLen) { return false; // invalid! + } proto = frameData[pos]; pos += ((unsigned int)frameData[pos + 1] * 8) + 8; break; @@ -732,7 +799,7 @@ static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsig bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,int32_t flowId) { - Metrics::pkt_frame++; + Metrics::pkt_frame_in++; int32_t _flowId = ZT_QOS_NO_FLOW; SharedPtr bond = peer->bond(); if (bond && bond->flowHashingSupported()) { @@ -810,8 +877,9 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const Shar const MAC sourceMac(peer->address(),nwid); const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; const uint8_t *const frameData = reinterpret_cast(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; - if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),sourceMac,network->mac(),frameData,frameLen,etherType,0) > 0) + if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),sourceMac,network->mac(),frameData,frameLen,etherType,0) > 0) { RR->node->putFrame(tPtr,nwid,network->userPtr(),sourceMac,network->mac(),etherType,0,(const void *)frameData,frameLen); + } } } else { _sendErrorNeedCredentials(RR,tPtr,peer,nwid); @@ -825,7 +893,7 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const Shar bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,int32_t flowId) { - Metrics::pkt_ext_frame++; + Metrics::pkt_ext_frame_in++; const uint64_t nwid = at(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID); const SharedPtr network(RR->node->network(nwid)); if (network) { @@ -835,8 +903,9 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const if ((flags & 0x01) != 0) { // inline COM with EXT_FRAME is deprecated but still used with old peers CertificateOfMembership com; comLen = com.deserialize(*this,ZT_PROTO_VERB_EXT_FRAME_IDX_COM); - if (com) + if (com) { network->addCredential(tPtr,com); + } } if (!network->gate(tPtr,peer)) { @@ -895,6 +964,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const const int64_t now = RR->node->now(); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -908,7 +978,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_echo++; + Metrics::pkt_echo_in++; uint64_t now = RR->node->now(); if (!_path->rateGateEchoRequest(now)) { return true; @@ -918,10 +988,12 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); outp.append((unsigned char)Packet::VERB_ECHO); outp.append((uint64_t)pid); - if (size() > ZT_PACKET_IDX_PAYLOAD) + if (size() > ZT_PACKET_IDX_PAYLOAD) { outp.append(reinterpret_cast(data()) + ZT_PACKET_IDX_PAYLOAD,size() - ZT_PACKET_IDX_PAYLOAD); + } outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_ECHO,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); @@ -931,7 +1003,7 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_multicast_like++; + Metrics::pkt_multicast_like_in++; const int64_t now = RR->node->now(); bool authorized = false; uint64_t lastNwid = 0; @@ -942,13 +1014,16 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,c if (nwid != lastNwid) { lastNwid = nwid; SharedPtr network(RR->node->network(nwid)); - if (network) + if (network) { authorized = network->gate(tPtr,peer); - if (!authorized) + } + if (!authorized) { authorized = ((RR->topology->amUpstream())||(RR->node->localControllerHasAuthorized(now,nwid,peer->address()))); + } } - if (authorized) + if (authorized) { RR->mc->add(tPtr,now,nwid,MulticastGroup(MAC(field(ptr + 8,6),6),at(ptr + 14)),peer->address()); + } } peer->received(tPtr,_path,hops(),packetId(),payloadLength(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); @@ -957,7 +1032,7 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,c bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_network_credentials++; + Metrics::pkt_network_credentials_in++; if (!peer->rateGateCredentialsReceived(RR->node->now())) { return true; } @@ -992,11 +1067,13 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t ++p; // skip trailing 0 after COMs if present if (p < size()) { // older ZeroTier versions do not send capabilities, tags, or revocations - const unsigned int numCapabilities = at(p); p += 2; + const unsigned int numCapabilities = at(p); + p += 2; for(unsigned int i=0;iid() != cap.networkId())) + if ((!network)||(network->id() != cap.networkId())) { network = RR->node->network(cap.networkId()); + } if (network) { switch (network->addCredential(tPtr,cap)) { case Membership::ADD_REJECTED: @@ -1011,13 +1088,17 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t } } - if (p >= size()) return true; + if (p >= size()) { + return true; + } - const unsigned int numTags = at(p); p += 2; + const unsigned int numTags = at(p); + p += 2; for(unsigned int i=0;iid() != tag.networkId())) + if ((!network)||(network->id() != tag.networkId())) { network = RR->node->network(tag.networkId()); + } if (network) { switch (network->addCredential(tPtr,tag)) { case Membership::ADD_REJECTED: @@ -1032,13 +1113,17 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t } } - if (p >= size()) return true; + if (p >= size()) { + return true; + } - const unsigned int numRevocations = at(p); p += 2; + const unsigned int numRevocations = at(p); + p += 2; for(unsigned int i=0;iid() != revocation.networkId())) + if ((!network)||(network->id() != revocation.networkId())) { network = RR->node->network(revocation.networkId()); + } if (network) { switch(network->addCredential(tPtr,peer->address(),revocation)) { case Membership::ADD_REJECTED: @@ -1053,13 +1138,17 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t } } - if (p >= size()) return true; + if (p >= size()) { + return true; + } - const unsigned int numCoos = at(p); p += 2; + const unsigned int numCoos = at(p); + p += 2; for(unsigned int i=0;iid() != coo.networkId())) + if ((!network)||(network->id() != coo.networkId())) { network = RR->node->network(coo.networkId()); + } if (network) { switch(network->addCredential(tPtr,coo)) { case Membership::ADD_REJECTED: @@ -1082,7 +1171,7 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *t bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_network_config_request++; + Metrics::pkt_network_config_request_in++; const uint64_t nwid = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID); const unsigned int hopCount = hops(); const uint64_t requestPacketId = packetId(); @@ -1099,6 +1188,8 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION); outp.append(nwid); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + Metrics::pkt_error_out++; + Metrics::pkt_error_unsupported_op_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -1109,7 +1200,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_network_config++; + Metrics::pkt_network_config_in++; const SharedPtr network(RR->node->network(at(ZT_PACKET_IDX_PAYLOAD))); if (network) { const uint64_t configUpdateId = network->handleConfigChunk(tPtr,packetId(),source(),*this,ZT_PACKET_IDX_PAYLOAD); @@ -1122,6 +1213,7 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c const int64_t now = RR->node->now(); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1133,7 +1225,7 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_multicast_gather++; + Metrics::pkt_multicast_gather_in++; const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID); const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS]; const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC,6),6),at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI)); @@ -1145,8 +1237,9 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr try { CertificateOfMembership com; com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM); - if ((com)&&(network)) + if ((com)&&(network)) { network->addCredential(tPtr,com); + } } catch ( ... ) {} // discard invalid COMs } @@ -1163,6 +1256,7 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr if (gatheredLocally > 0) { outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),now); } } @@ -1174,7 +1268,7 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_multicast_frame++; + Metrics::pkt_multicast_frame_in++; const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID); const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS]; @@ -1187,8 +1281,9 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, // This is deprecated but may still be sent by old peers CertificateOfMembership com; offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM); - if (com) + if (com) { network->addCredential(tPtr,com); + } } if (!network->gate(tPtr,peer)) { @@ -1234,8 +1329,9 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, const uint8_t *const frameData = (const uint8_t *)field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME,frameLen); - if ((flags & 0x08)&&(network->config().isMulticastReplicator(RR->identity.address()))) + if ((flags & 0x08)&&(network->config().isMulticastReplicator(RR->identity.address()))) { RR->mc->send(tPtr,RR->node->now(),network,peer->address(),to,from,etherType,frameData,frameLen); + } if (from != MAC(peer->address(),nwid)) { if (network->config().permitsBridging(peer->address())) { @@ -1247,8 +1343,9 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, } } - if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),from,to.mac(),frameData,frameLen,etherType,0) > 0) + if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),from,to.mac(),frameData,frameLen,etherType,0) > 0) { RR->node->putFrame(tPtr,nwid,network->userPtr(),from,to.mac(),etherType,0,(const void *)frameData,frameLen); + } } if (gatherLimit) { @@ -1263,6 +1360,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, const int64_t now = RR->node->now(); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); + Metrics::pkt_ok_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1275,7 +1373,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_push_direct_paths++; + Metrics::pkt_push_direct_paths_in++; const int64_t now = RR->node->now(); if (!peer->rateGatePushDirectPaths(now)) { @@ -1292,7 +1390,8 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPt while (count--) { // if ptr overflows Buffer will throw unsigned int flags = (*this)[ptr++]; - unsigned int extLen = at(ptr); ptr += 2; + unsigned int extLen = at(ptr); + ptr += 2; ptr += extLen; // unused right now unsigned int addrType = (*this)[ptr++]; unsigned int addrLen = (*this)[ptr++]; @@ -1338,7 +1437,7 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPt bool IncomingPacket::_doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_user_message++; + Metrics::pkt_user_message_in++; if (likely(size() >= (ZT_PACKET_IDX_PAYLOAD + 8))) { ZT_UserMessage um; um.origin = peer->address().toInt(); @@ -1355,7 +1454,7 @@ bool IncomingPacket::_doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,con bool IncomingPacket::_doREMOTE_TRACE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_remote_trace++; + Metrics::pkt_remote_trace_in++; ZT_RemoteTrace rt; const char *ptr = reinterpret_cast(data()) + ZT_PACKET_IDX_PAYLOAD; const char *const eof = reinterpret_cast(data()) + size(); @@ -1380,7 +1479,7 @@ bool IncomingPacket::_doREMOTE_TRACE(const RuntimeEnvironment *RR,void *tPtr,con bool IncomingPacket::_doPATH_NEGOTIATION_REQUEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) { - Metrics::pkt_path_negotiation_request++; + Metrics::pkt_path_negotiation_request_in++; uint64_t now = RR->node->now(); SharedPtr bond = peer->bond(); if (!bond || !bond->rateGatePathNegotiation(now, _path)) { @@ -1405,6 +1504,8 @@ void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE); outp.append(nwid); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + Metrics::pkt_error_out++; + Metrics::pkt_error_need_membership_cert_out++; _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp index 07c53662f..da1c7294c 100644 --- a/node/InetAddress.cpp +++ b/node/InetAddress.cpp @@ -33,46 +33,80 @@ InetAddress::IpScope InetAddress::ipScope() const case AF_INET: { const uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr); switch(ip >> 24) { - case 0x00: return IP_SCOPE_NONE; // 0.0.0.0/8 (reserved, never used) - case 0x06: return IP_SCOPE_PSEUDOPRIVATE; // 6.0.0.0/8 (US Army) - case 0x0a: return IP_SCOPE_PRIVATE; // 10.0.0.0/8 - case 0x0b: return IP_SCOPE_PSEUDOPRIVATE; // 11.0.0.0/8 (US DoD) - case 0x15: return IP_SCOPE_PSEUDOPRIVATE; // 21.0.0.0/8 (US DDN-RVN) - case 0x16: return IP_SCOPE_PSEUDOPRIVATE; // 22.0.0.0/8 (US DISA) - case 0x19: return IP_SCOPE_PSEUDOPRIVATE; // 25.0.0.0/8 (UK Ministry of Defense) - case 0x1a: return IP_SCOPE_PSEUDOPRIVATE; // 26.0.0.0/8 (US DISA) - case 0x1c: return IP_SCOPE_PSEUDOPRIVATE; // 28.0.0.0/8 (US DSI-North) - case 0x1d: return IP_SCOPE_PSEUDOPRIVATE; // 29.0.0.0/8 (US DISA) - case 0x1e: return IP_SCOPE_PSEUDOPRIVATE; // 30.0.0.0/8 (US DISA) - case 0x33: return IP_SCOPE_PSEUDOPRIVATE; // 51.0.0.0/8 (UK Department of Social Security) - case 0x37: return IP_SCOPE_PSEUDOPRIVATE; // 55.0.0.0/8 (US DoD) - case 0x38: return IP_SCOPE_PSEUDOPRIVATE; // 56.0.0.0/8 (US Postal Service) + case 0x00: + return IP_SCOPE_NONE; // 0.0.0.0/8 (reserved, never used) + case 0x06: + return IP_SCOPE_PSEUDOPRIVATE; // 6.0.0.0/8 (US Army) + case 0x0a: + return IP_SCOPE_PRIVATE; // 10.0.0.0/8 + case 0x0b: + return IP_SCOPE_PSEUDOPRIVATE; // 11.0.0.0/8 (US DoD) + case 0x15: + return IP_SCOPE_PSEUDOPRIVATE; // 21.0.0.0/8 (US DDN-RVN) + case 0x16: + return IP_SCOPE_PSEUDOPRIVATE; // 22.0.0.0/8 (US DISA) + case 0x19: + return IP_SCOPE_PSEUDOPRIVATE; // 25.0.0.0/8 (UK Ministry of Defense) + case 0x1a: + return IP_SCOPE_PSEUDOPRIVATE; // 26.0.0.0/8 (US DISA) + case 0x1c: + return IP_SCOPE_PSEUDOPRIVATE; // 28.0.0.0/8 (US DSI-North) + case 0x1d: + return IP_SCOPE_PSEUDOPRIVATE; // 29.0.0.0/8 (US DISA) + case 0x1e: + return IP_SCOPE_PSEUDOPRIVATE; // 30.0.0.0/8 (US DISA) + case 0x33: + return IP_SCOPE_PSEUDOPRIVATE; // 51.0.0.0/8 (UK Department of Social Security) + case 0x37: + return IP_SCOPE_PSEUDOPRIVATE; // 55.0.0.0/8 (US DoD) + case 0x38: + return IP_SCOPE_PSEUDOPRIVATE; // 56.0.0.0/8 (US Postal Service) case 0x64: - if ((ip & 0xffc00000) == 0x64400000) return IP_SCOPE_PRIVATE; // 100.64.0.0/10 + if ((ip & 0xffc00000) == 0x64400000) { + return IP_SCOPE_PRIVATE; // 100.64.0.0/10 + } break; - case 0x7f: return IP_SCOPE_LOOPBACK; // 127.0.0.0/8 + case 0x7f: + return IP_SCOPE_LOOPBACK; // 127.0.0.0/8 case 0xa9: - if ((ip & 0xffff0000) == 0xa9fe0000) return IP_SCOPE_LINK_LOCAL; // 169.254.0.0/16 + if ((ip & 0xffff0000) == 0xa9fe0000) { + return IP_SCOPE_LINK_LOCAL; // 169.254.0.0/16 + } break; case 0xac: - if ((ip & 0xfff00000) == 0xac100000) return IP_SCOPE_PRIVATE; // 172.16.0.0/12 + if ((ip & 0xfff00000) == 0xac100000) { + return IP_SCOPE_PRIVATE; // 172.16.0.0/12 + } break; case 0xc0: - if ((ip & 0xffff0000) == 0xc0a80000) return IP_SCOPE_PRIVATE; // 192.168.0.0/16 - if ((ip & 0xffffff00) == 0xc0000200) return IP_SCOPE_PRIVATE; // 192.0.2.0/24 + if ((ip & 0xffff0000) == 0xc0a80000) { + return IP_SCOPE_PRIVATE; // 192.168.0.0/16 + } + if ((ip & 0xffffff00) == 0xc0000200) { + return IP_SCOPE_PRIVATE; // 192.0.2.0/24 + } break; case 0xc6: - if ((ip & 0xfffe0000) == 0xc6120000) return IP_SCOPE_PRIVATE; // 198.18.0.0/15 - if ((ip & 0xffffff00) == 0xc6336400) return IP_SCOPE_PRIVATE; // 198.51.100.0/24 + if ((ip & 0xfffe0000) == 0xc6120000) { + return IP_SCOPE_PRIVATE; // 198.18.0.0/15 + } + if ((ip & 0xffffff00) == 0xc6336400) { + return IP_SCOPE_PRIVATE; // 198.51.100.0/24 + } break; case 0xcb: - if ((ip & 0xffffff00) == 0xcb007100) return IP_SCOPE_PRIVATE; // 203.0.113.0/24 + if ((ip & 0xffffff00) == 0xcb007100) { + return IP_SCOPE_PRIVATE; // 203.0.113.0/24 + } break; - case 0xff: return IP_SCOPE_NONE; // 255.0.0.0/8 (broadcast, or unused/unusable) + case 0xff: + return IP_SCOPE_NONE; // 255.0.0.0/8 (broadcast, or unused/unusable) } switch(ip >> 28) { - case 0xe: return IP_SCOPE_MULTICAST; // 224.0.0.0/4 - case 0xf: return IP_SCOPE_PSEUDOPRIVATE; // 240.0.0.0/4 ("reserved," usually unusable) + case 0xe: + return IP_SCOPE_MULTICAST; // 224.0.0.0/4 + case 0xf: + return IP_SCOPE_PSEUDOPRIVATE; // 240.0.0.0/4 ("reserved," usually unusable) } return IP_SCOPE_GLOBAL; } break; @@ -80,21 +114,35 @@ InetAddress::IpScope InetAddress::ipScope() const case AF_INET6: { const unsigned char *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); if ((ip[0] & 0xf0) == 0xf0) { - if (ip[0] == 0xff) return IP_SCOPE_MULTICAST; // ff00::/8 + if (ip[0] == 0xff) { + return IP_SCOPE_MULTICAST; // ff00::/8 + } if ((ip[0] == 0xfe)&&((ip[1] & 0xc0) == 0x80)) { unsigned int k = 2; - while ((!ip[k])&&(k < 15)) ++k; - if ((k == 15)&&(ip[15] == 0x01)) - return IP_SCOPE_LOOPBACK; // fe80::1/128 - else return IP_SCOPE_LINK_LOCAL; // fe80::/10 + while ((!ip[k])&&(k < 15)) { + ++k; + } + if ((k == 15)&&(ip[15] == 0x01)) { + return IP_SCOPE_LOOPBACK; // fe80::1/128 + } else { + return IP_SCOPE_LINK_LOCAL; // fe80::/10 + } + } + if ((ip[0] & 0xfe) == 0xfc) { + return IP_SCOPE_PRIVATE; // fc00::/7 } - if ((ip[0] & 0xfe) == 0xfc) return IP_SCOPE_PRIVATE; // fc00::/7 } unsigned int k = 0; - while ((!ip[k])&&(k < 15)) ++k; + while ((!ip[k])&&(k < 15)) { + ++k; + } if (k == 15) { // all 0's except last byte - if (ip[15] == 0x01) return IP_SCOPE_LOOPBACK; // ::1/128 - if (ip[15] == 0x00) return IP_SCOPE_NONE; // ::/128 + if (ip[15] == 0x01) { + return IP_SCOPE_LOOPBACK; // ::1/128 + } + if (ip[15] == 0x00) { + return IP_SCOPE_NONE; // ::/128 + } } return IP_SCOPE_GLOBAL; } break; @@ -124,7 +172,9 @@ char *InetAddress::toString(char buf[64]) const { char *p = toIpString(buf); if (*p) { - while (*p) ++p; + while (*p) { + ++p; + } *(p++) = '/'; Utils::decimal(port(),p); } @@ -160,14 +210,17 @@ bool InetAddress::fromString(const char *ipSlashPort) memset(this,0,sizeof(InetAddress)); - if (!*ipSlashPort) + if (!*ipSlashPort) { return true; - if (!Utils::scopy(buf,sizeof(buf),ipSlashPort)) + } + if (!Utils::scopy(buf,sizeof(buf),ipSlashPort)) { return false; + } char *portAt = buf; - while ((*portAt)&&(*portAt != '/')) + while ((*portAt)&&(*portAt != '/')) { ++portAt; + } unsigned int port = 0; if (*portAt) { *(portAt++) = (char)0; @@ -255,8 +308,9 @@ bool InetAddress::isEqualPrefix(const InetAddress &addr) const const uint8_t *a = reinterpret_cast(reinterpret_cast(&addr)->sin6_addr.s6_addr); const uint8_t *b = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); for(unsigned int i=0;i<16;++i) { - if ((a[i] & m[i]) != (b[i] & n[i])) + if ((a[i] & m[i]) != (b[i] & n[i])) { return false; + } } return true; } @@ -271,8 +325,9 @@ bool InetAddress::containsAddress(const InetAddress &addr) const switch(ss_family) { case AF_INET: { const unsigned int bits = netmaskBits(); - if (bits == 0) + if (bits == 0) { return true; + } return ( (Utils::ntoh((uint32_t)reinterpret_cast(&addr)->sin_addr.s_addr) >> (32 - bits)) == (Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr) >> (32 - bits)) ); } case AF_INET6: { @@ -281,8 +336,9 @@ bool InetAddress::containsAddress(const InetAddress &addr) const const uint8_t *a = reinterpret_cast(reinterpret_cast(&addr)->sin6_addr.s6_addr); const uint8_t *b = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); for(unsigned int i=0;i<16;++i) { - if ((a[i] & m[i]) != b[i]) + if ((a[i] & m[i]) != b[i]) { return false; + } } return true; } @@ -296,26 +352,32 @@ bool InetAddress::isNetwork() const switch(ss_family) { case AF_INET: { unsigned int bits = netmaskBits(); - if (bits <= 0) + if (bits <= 0) { return false; - if (bits >= 32) + } + if (bits >= 32) { return false; + } uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr); return ((ip & (0xffffffff >> bits)) == 0); } case AF_INET6: { unsigned int bits = netmaskBits(); - if (bits <= 0) + if (bits <= 0) { return false; - if (bits >= 128) + } + if (bits >= 128) { return false; + } const unsigned char *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); unsigned int p = bits / 8; - if ((ip[p++] & (0xff >> (bits % 8))) != 0) + if ((ip[p++] & (0xff >> (bits % 8))) != 0) { return false; + } while (p < 16) { - if (ip[p++]) + if (ip[p++]) { return false; + } } return true; } @@ -348,30 +410,32 @@ bool InetAddress::operator==(const InetAddress &a) const bool InetAddress::operator<(const InetAddress &a) const { - if (ss_family < a.ss_family) + if (ss_family < a.ss_family) { return true; - else if (ss_family == a.ss_family) { + } else if (ss_family == a.ss_family) { switch(ss_family) { case AF_INET: - if (reinterpret_cast(this)->sin_port < reinterpret_cast(&a)->sin_port) + if (reinterpret_cast(this)->sin_port < reinterpret_cast(&a)->sin_port) { return true; - else if (reinterpret_cast(this)->sin_port == reinterpret_cast(&a)->sin_port) { - if (reinterpret_cast(this)->sin_addr.s_addr < reinterpret_cast(&a)->sin_addr.s_addr) + } else if (reinterpret_cast(this)->sin_port == reinterpret_cast(&a)->sin_port) { + if (reinterpret_cast(this)->sin_addr.s_addr < reinterpret_cast(&a)->sin_addr.s_addr) { return true; + } } break; case AF_INET6: - if (reinterpret_cast(this)->sin6_port < reinterpret_cast(&a)->sin6_port) + if (reinterpret_cast(this)->sin6_port < reinterpret_cast(&a)->sin6_port) { return true; - else if (reinterpret_cast(this)->sin6_port == reinterpret_cast(&a)->sin6_port) { - if (reinterpret_cast(this)->sin6_flowinfo < reinterpret_cast(&a)->sin6_flowinfo) + } else if (reinterpret_cast(this)->sin6_port == reinterpret_cast(&a)->sin6_port) { + if (reinterpret_cast(this)->sin6_flowinfo < reinterpret_cast(&a)->sin6_flowinfo) { return true; - else if (reinterpret_cast(this)->sin6_flowinfo == reinterpret_cast(&a)->sin6_flowinfo) { - if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) < 0) + } else if (reinterpret_cast(this)->sin6_flowinfo == reinterpret_cast(&a)->sin6_flowinfo) { + if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) < 0) { return true; - else if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0) { - if (reinterpret_cast(this)->sin6_scope_id < reinterpret_cast(&a)->sin6_scope_id) + } else if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0) { + if (reinterpret_cast(this)->sin6_scope_id < reinterpret_cast(&a)->sin6_scope_id) { return true; + } } } } diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp index a9a35dd20..4a3dd80cb 100644 --- a/node/InetAddress.hpp +++ b/node/InetAddress.hpp @@ -94,29 +94,33 @@ struct InetAddress : public sockaddr_storage inline InetAddress &operator=(const InetAddress &a) { - if (&a != this) + if (&a != this) { memcpy(this,&a,sizeof(InetAddress)); + } return *this; } inline InetAddress &operator=(const InetAddress *a) { - if (a != this) + if (a != this) { memcpy(this,a,sizeof(InetAddress)); + } return *this; } inline InetAddress &operator=(const struct sockaddr_storage &ss) { - if (reinterpret_cast(&ss) != this) + if (reinterpret_cast(&ss) != this) { memcpy(this,&ss,sizeof(InetAddress)); + } return *this; } inline InetAddress &operator=(const struct sockaddr_storage *ss) { - if (reinterpret_cast(ss) != this) + if (reinterpret_cast(ss) != this) { memcpy(this,ss,sizeof(InetAddress)); + } return *this; } @@ -230,8 +234,9 @@ struct InetAddress : public sockaddr_storage case AF_INET6: const uint8_t *ipb = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); for(int i=0;i<16;++i) { - if (ipb[i]) + if (ipb[i]) { return false; + } } return (reinterpret_cast(this)->sin6_port == 0); } @@ -260,9 +265,12 @@ struct InetAddress : public sockaddr_storage inline unsigned int port() const { switch(ss_family) { - case AF_INET: return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin_port)); - case AF_INET6: return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin6_port)); - default: return 0; + case AF_INET: + return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin_port)); + case AF_INET6: + return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin6_port)); + default: + return 0; } } @@ -284,8 +292,10 @@ struct InetAddress : public sockaddr_storage { const unsigned int n = port(); switch(ss_family) { - case AF_INET: return (n <= 32); - case AF_INET6: return (n <= 128); + case AF_INET: + return (n <= 32); + case AF_INET6: + return (n <= 128); } return false; } @@ -356,9 +366,12 @@ struct InetAddress : public sockaddr_storage inline const void *rawIpData() const { switch(ss_family) { - case AF_INET: return (const void *)&(reinterpret_cast(this)->sin_addr.s_addr); - case AF_INET6: return (const void *)(reinterpret_cast(this)->sin6_addr.s6_addr); - default: return 0; + case AF_INET: + return (const void *)&(reinterpret_cast(this)->sin_addr.s_addr); + case AF_INET6: + return (const void *)(reinterpret_cast(this)->sin6_addr.s6_addr); + default: + return 0; } } @@ -390,10 +403,12 @@ struct InetAddress : public sockaddr_storage inline bool ipsEqual(const InetAddress &a) const { if (ss_family == a.ss_family) { - if (ss_family == AF_INET) + if (ss_family == AF_INET) { return (reinterpret_cast(this)->sin_addr.s_addr == reinterpret_cast(&a)->sin_addr.s_addr); - if (ss_family == AF_INET6) + } + if (ss_family == AF_INET6) { return (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0); + } return (memcmp(this,&a,sizeof(InetAddress)) == 0); } return false; @@ -410,10 +425,12 @@ struct InetAddress : public sockaddr_storage inline bool ipsEqual2(const InetAddress &a) const { if (ss_family == a.ss_family) { - if (ss_family == AF_INET) + if (ss_family == AF_INET) { return (reinterpret_cast(this)->sin_addr.s_addr == reinterpret_cast(&a)->sin_addr.s_addr); - if (ss_family == AF_INET6) - return (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,8) == 0); + } + if (ss_family == AF_INET6) { + return (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr, reinterpret_cast(&a)->sin6_addr.s6_addr, 8) == 0); + } return (memcmp(this,&a,sizeof(InetAddress)) == 0); } return false; @@ -426,14 +443,16 @@ struct InetAddress : public sockaddr_storage } else if (ss_family == AF_INET6) { unsigned long tmp = reinterpret_cast(this)->sin6_port; const uint8_t *a = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); - for(long i=0;i<16;++i) + for(long i=0;i<16;++i) { reinterpret_cast(&tmp)[i % sizeof(tmp)] ^= a[i]; + } return tmp; } else { unsigned long tmp = reinterpret_cast(this)->sin6_port; const uint8_t *a = reinterpret_cast(this); - for(long i=0;i<(long)sizeof(InetAddress);++i) + for(long i=0;i<(long)sizeof(InetAddress);++i) { reinterpret_cast(&tmp)[i % sizeof(tmp)] ^= a[i]; + } return tmp; } } @@ -470,8 +489,9 @@ struct InetAddress : public sockaddr_storage while ((ip0 >> 31) == (ip1 >> 31)) { ip0 <<= 1; ip1 <<= 1; - if (++c == 32) + if (++c == 32) { break; + } } } break; case AF_INET6: { @@ -485,8 +505,9 @@ struct InetAddress : public sockaddr_storage uint8_t ip1b = ip1[i]; uint8_t bit = 0x80; while (bit != 0) { - if ((ip0b & bit) != (ip1b & bit)) + if ((ip0b & bit) != (ip1b & bit)) { break; + } ++c; bit >>= 1; } @@ -512,11 +533,16 @@ struct InetAddress : public sockaddr_storage break; case AF_INET6: { const uint8_t *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); - h = ((unsigned long)ip[0]); h <<= 1; - h += ((unsigned long)ip[1]); h <<= 1; - h += ((unsigned long)ip[2]); h <<= 1; - h += ((unsigned long)ip[3]); h <<= 1; - h += ((unsigned long)ip[4]); h <<= 1; + h = ((unsigned long)ip[0]); + h <<= 1; + h += ((unsigned long)ip[1]); + h <<= 1; + h += ((unsigned long)ip[2]); + h <<= 1; + h += ((unsigned long)ip[3]); + h <<= 1; + h += ((unsigned long)ip[4]); + h <<= 1; h += ((unsigned long)ip[5]); } break; } @@ -570,13 +596,17 @@ struct InetAddress : public sockaddr_storage return (unsigned int)(b.template at(p) + 3); // other addresses begin with 16-bit non-inclusive length case 0x04: ss_family = AF_INET; - memcpy(&(reinterpret_cast(this)->sin_addr.s_addr),b.field(p,4),4); p += 4; - reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); p += 2; + memcpy(&(reinterpret_cast(this)->sin_addr.s_addr),b.field(p,4),4); + p += 4; + reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); + p += 2; break; case 0x06: ss_family = AF_INET6; - memcpy(reinterpret_cast(this)->sin6_addr.s6_addr,b.field(p,16),16); p += 16; - reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); p += 2; + memcpy(reinterpret_cast(this)->sin6_addr.s6_addr,b.field(p,16),16); + p += 16; + reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); + p += 2; break; default: throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING; diff --git a/node/MAC.hpp b/node/MAC.hpp index de72a1aed..ca4eacf6f 100644 --- a/node/MAC.hpp +++ b/node/MAC.hpp @@ -71,11 +71,16 @@ public: return; } const unsigned char *b = (const unsigned char *)bits; - _m = ((((uint64_t)*b) & 0xff) << 40); ++b; - _m |= ((((uint64_t)*b) & 0xff) << 32); ++b; - _m |= ((((uint64_t)*b) & 0xff) << 24); ++b; - _m |= ((((uint64_t)*b) & 0xff) << 16); ++b; - _m |= ((((uint64_t)*b) & 0xff) << 8); ++b; + _m = ((((uint64_t)*b) & 0xff) << 40); + ++b; + _m |= ((((uint64_t)*b) & 0xff) << 32); + ++b; + _m |= ((((uint64_t)*b) & 0xff) << 24); + ++b; + _m |= ((((uint64_t)*b) & 0xff) << 16); + ++b; + _m |= ((((uint64_t)*b) & 0xff) << 8); + ++b; _m |= (((uint64_t)*b) & 0xff); } @@ -85,8 +90,9 @@ public: */ inline void copyTo(void *buf,unsigned int len) const { - if (len < 6) + if (len < 6) { return; + } unsigned char *b = (unsigned char *)buf; *(b++) = (unsigned char)((_m >> 40) & 0xff); *(b++) = (unsigned char)((_m >> 32) & 0xff); diff --git a/node/Membership.cpp b/node/Membership.cpp index 8ddd61f09..1b317d94f 100644 --- a/node/Membership.cpp +++ b/node/Membership.cpp @@ -39,18 +39,21 @@ void Membership::pushCredentials(const RuntimeEnvironment *RR,void *tPtr,const i { const Capability *sendCaps[ZT_MAX_NETWORK_CAPABILITIES]; unsigned int sendCapCount = 0; - for(unsigned int c=0;csw->send(tPtr,outp,true); + Metrics::pkt_network_credentials_out++; } _lastPushedCredentials = now; @@ -115,8 +119,9 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme RR->t->credentialRejected(tPtr,com,"old"); return ADD_REJECTED; } - if (_com == com) + if (_com == com) { return ADD_ACCEPTED_REDUNDANT; + } switch(com.verify(RR,tPtr)) { default: @@ -141,8 +146,9 @@ static Membership::AddCredentialResult _addCredImpl(Hashtable &remot RR->t->credentialRejected(tPtr,cred,"old"); return Membership::ADD_REJECTED; } - if (*rc == cred) + if (*rc == cred) { return Membership::ADD_ACCEPTED_REDUNDANT; + } } const int64_t *const rt = revocations.get(Membership::credentialKey(C::credentialType(),cred.id())); @@ -156,8 +162,9 @@ static Membership::AddCredentialResult _addCredImpl(Hashtable &remot RR->t->credentialRejected(tPtr,cred,"invalid"); return Membership::ADD_REJECTED; case 0: - if (!rc) + if (!rc) { rc = &(remoteCreds[cred.id()]); + } *rc = cred; return Membership::ADD_ACCEPTED_NEW; case 1: diff --git a/node/Membership.hpp b/node/Membership.hpp index 21561a18e..35b341fd4 100644 --- a/node/Membership.hpp +++ b/node/Membership.hpp @@ -115,8 +115,9 @@ public: CertificateOfOwnership *v = (CertificateOfOwnership *)0; Hashtable< uint32_t,CertificateOfOwnership >::Iterator i(*(const_cast< Hashtable< uint32_t,CertificateOfOwnership> *>(&_remoteCoos))); while (i.next(k,v)) { - if (_isCredentialTimestampValid(nconf,*v)&&(v->owns(r))) + if (_isCredentialTimestampValid(nconf,*v)&&(v->owns(r))) { return true; + } } return _isV6NDPEmulated(nconf,r); } @@ -187,8 +188,9 @@ private: break; } } - if (prefixMatches) + if (prefixMatches) { return true; + } break; } } @@ -203,8 +205,9 @@ private: break; } } - if (prefixMatches) + if (prefixMatches) { return true; + } break; } } @@ -230,8 +233,9 @@ private: C *v = (C *)0; typename Hashtable::Iterator i(remoteCreds); while (i.next(k,v)) { - if (!_isCredentialTimestampValid(nconf,*v)) + if (!_isCredentialTimestampValid(nconf,*v)) { remoteCreds.erase(*k); + } } } @@ -271,8 +275,9 @@ public: inline Capability *next() { while (_hti.next(_k,_c)) { - if (_m._isCredentialTimestampValid(_nconf,*_c)) + if (_m._isCredentialTimestampValid(_nconf,*_c)) { return _c; + } } return (Capability *)0; } diff --git a/node/Metrics.cpp b/node/Metrics.cpp index 8ccef869f..0b4d3495b 100644 --- a/node/Metrics.cpp +++ b/node/Metrics.cpp @@ -11,6 +11,7 @@ */ #include +#include namespace prometheus { namespace simpleapi { @@ -24,75 +25,172 @@ namespace ZeroTier { namespace Metrics { // Packet Type Counts prometheus::simpleapi::counter_family_t packets - { "zt_packet_incoming", "incoming packet type counts"}; - prometheus::simpleapi::counter_metric_t pkt_error - { packets.Add({{"packet_type", "error"}}) }; - prometheus::simpleapi::counter_metric_t pkt_ack - { packets.Add({{"packet_type", "ack"}}) }; - prometheus::simpleapi::counter_metric_t pkt_qos - { packets.Add({{"packet_type", "qos"}}) }; - prometheus::simpleapi::counter_metric_t pkt_hello - { packets.Add({{"packet_type", "hello"}}) }; - prometheus::simpleapi::counter_metric_t pkt_ok - { packets.Add({{"packet_type", "ok"}}) }; - prometheus::simpleapi::counter_metric_t pkt_whois - { packets.Add({{"packet_type", "whois"}}) }; - prometheus::simpleapi::counter_metric_t pkt_rendezvous - { packets.Add({{"packet_type", "rendezvous"}}) }; - prometheus::simpleapi::counter_metric_t pkt_frame - { packets.Add({{"packet_type", "frame"}}) }; - prometheus::simpleapi::counter_metric_t pkt_ext_frame - { packets.Add({{"packet_type", "ext_frame"}}) }; - prometheus::simpleapi::counter_metric_t pkt_echo - { packets.Add({{"packet_type", "echo"}}) }; - prometheus::simpleapi::counter_metric_t pkt_multicast_like - { packets.Add({{"packet_type", "multicast_like"}}) }; - prometheus::simpleapi::counter_metric_t pkt_network_credentials - { packets.Add({{"packet_type", "network_credentials"}}) }; - prometheus::simpleapi::counter_metric_t pkt_network_config_request - { packets.Add({{"packet_type", "network_config_request"}}) }; - prometheus::simpleapi::counter_metric_t pkt_network_config - { packets.Add({{"packet_type", "network_config"}}) }; - prometheus::simpleapi::counter_metric_t pkt_multicast_gather - { packets.Add({{"packet_type", "multicast_gather"}}) }; - prometheus::simpleapi::counter_metric_t pkt_multicast_frame - { packets.Add({{"packet_type", "multicast_frame"}}) }; - prometheus::simpleapi::counter_metric_t pkt_push_direct_paths - { packets.Add({{"packet_type", "push_direct_paths"}}) }; - prometheus::simpleapi::counter_metric_t pkt_user_message - { packets.Add({{"packet_type", "user_message"}}) }; - prometheus::simpleapi::counter_metric_t pkt_remote_trace - { packets.Add({{"packet_type", "remote_trace"}}) }; - prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request - { packets.Add({{"packet_type", "path_negotiation_request"}}) }; + { "zt_packet", "ZeroTier packet type counts"}; + + // Incoming packets + prometheus::simpleapi::counter_metric_t pkt_nop_in + { packets.Add({{"packet_type", "nop"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_in + { packets.Add({{"packet_type", "error"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ack_in + { packets.Add({{"packet_type", "ack"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_qos_in + { packets.Add({{"packet_type", "qos"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_hello_in + { packets.Add({{"packet_type", "hello"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ok_in + { packets.Add({{"packet_type", "ok"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_whois_in + { packets.Add({{"packet_type", "whois"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_rendezvous_in + { packets.Add({{"packet_type", "rendezvous"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_frame_in + { packets.Add({{"packet_type", "frame"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ext_frame_in + { packets.Add({{"packet_type", "ext_frame"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_echo_in + { packets.Add({{"packet_type", "echo"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_like_in + { packets.Add({{"packet_type", "multicast_like"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_credentials_in + { packets.Add({{"packet_type", "network_credentials"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_config_request_in + { packets.Add({{"packet_type", "network_config_request"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_config_in + { packets.Add({{"packet_type", "network_config"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_gather_in + { packets.Add({{"packet_type", "multicast_gather"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_frame_in + { packets.Add({{"packet_type", "multicast_frame"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_push_direct_paths_in + { packets.Add({{"packet_type", "push_direct_paths"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_user_message_in + { packets.Add({{"packet_type", "user_message"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_remote_trace_in + { packets.Add({{"packet_type", "remote_trace"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request_in + { packets.Add({{"packet_type", "path_negotiation_request"}, {"direction", "rx"}}) }; + + // Outgoing packets + prometheus::simpleapi::counter_metric_t pkt_nop_out + { packets.Add({{"packet_type", "nop"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_out + { packets.Add({{"packet_type", "error"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ack_out + { packets.Add({{"packet_type", "ack"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_qos_out + { packets.Add({{"packet_type", "qos"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_hello_out + { packets.Add({{"packet_type", "hello"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ok_out + { packets.Add({{"packet_type", "ok"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_whois_out + { packets.Add({{"packet_type", "whois"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_rendezvous_out + { packets.Add({{"packet_type", "rendezvous"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_frame_out + { packets.Add({{"packet_type", "frame"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_ext_frame_out + { packets.Add({{"packet_type", "ext_frame"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_echo_out + { packets.Add({{"packet_type", "echo"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_like_out + { packets.Add({{"packet_type", "multicast_like"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_credentials_out + { packets.Add({{"packet_type", "network_credentials"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_config_request_out + { packets.Add({{"packet_type", "network_config_request"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_network_config_out + { packets.Add({{"packet_type", "network_config"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_gather_out + { packets.Add({{"packet_type", "multicast_gather"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_multicast_frame_out + { packets.Add({{"packet_type", "multicast_frame"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_push_direct_paths_out + { packets.Add({{"packet_type", "push_direct_paths"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_user_message_out + { packets.Add({{"packet_type", "user_message"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_remote_trace_out + { packets.Add({{"packet_type", "remote_trace"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request_out + { packets.Add({{"packet_type", "path_negotiation_request"}, {"direction", "tx"}}) }; + // Packet Error Counts prometheus::simpleapi::counter_family_t packet_errors - { "zt_packet_incoming_error", "incoming packet errors"}; - prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found - { packet_errors.Add({{"error_type", "obj_not_found"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op - { packet_errors.Add({{"error_type", "unsupported_operation"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_identity_collision - { packet_errors.Add({{"error_type", "identity_collision"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert - { packet_errors.Add({{"error_type", "need_membership_certificate"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied - { packet_errors.Add({{"error_type", "network_access_denied"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast - { packet_errors.Add({{"error_type", "unwanted_multicast"}}) }; - prometheus::simpleapi::counter_metric_t pkt_error_authentication_required - { packet_errors.Add({{"error_type", "authentication_required"}}) }; + { "zt_packet_error", "ZeroTier packet errors"}; + + // Incoming Error Counts + prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_in + { packet_errors.Add({{"error_type", "obj_not_found"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op_in + { packet_errors.Add({{"error_type", "unsupported_operation"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_identity_collision_in + { packet_errors.Add({{"error_type", "identity_collision"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert_in + { packet_errors.Add({{"error_type", "need_membership_certificate"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied_in + { packet_errors.Add({{"error_type", "network_access_denied"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast_in + { packet_errors.Add({{"error_type", "unwanted_multicast"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_in + { packet_errors.Add({{"error_type", "authentication_required"}, {"direction", "rx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_in + { packet_errors.Add({{"error_type", "internal_server_error"}, {"direction", "rx"}}) }; + + // Outgoing Error Counts + prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_out + { packet_errors.Add({{"error_type", "obj_not_found"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op_out + { packet_errors.Add({{"error_type", "unsupported_operation"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_identity_collision_out + { packet_errors.Add({{"error_type", "identity_collision"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert_out + { packet_errors.Add({{"error_type", "need_membership_certificate"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied_out + { packet_errors.Add({{"error_type", "network_access_denied"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast_out + { packet_errors.Add({{"error_type", "unwanted_multicast"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_out + { packet_errors.Add({{"error_type", "authentication_required"}, {"direction", "tx"}}) }; + prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_out + { packet_errors.Add({{"error_type", "internal_server_error"}, {"direction", "tx"}}) }; // Data Sent/Received Metrics - prometheus::simpleapi::counter_metric_t udp_send - { "zt_udp_data_sent", "number of bytes ZeroTier has sent via UDP" }; + prometheus::simpleapi::counter_family_t data + { "zt_data", "number of bytes ZeroTier has transmitted or received" }; prometheus::simpleapi::counter_metric_t udp_recv - { "zt_udp_data_recv", "number of bytes ZeroTier has received via UDP" }; + { data.Add({{"protocol","udp"},{"direction","rx"}}) }; + prometheus::simpleapi::counter_metric_t udp_send + { data.Add({{"protocol","udp"},{"direction","tx"}}) }; prometheus::simpleapi::counter_metric_t tcp_send - { "zt_tcp_data_sent", "number of bytes ZeroTier has sent via TCP" }; + { data.Add({{"protocol","tcp"},{"direction", "tx"}}) }; prometheus::simpleapi::counter_metric_t tcp_recv - { "zt_tcp_data_recv", "number of bytes ZeroTier has received via TCP" }; + { data.Add({{"protocol","tcp"},{"direction", "rx"}}) }; + + // Network Metrics + prometheus::simpleapi::gauge_metric_t network_num_joined + { "zt_num_networks", "number of networks this instance is joined to" }; + prometheus::simpleapi::gauge_family_t network_num_multicast_groups + { "zt_network_multicast_groups_subscribed", "number of multicast groups networks are subscribed to" }; + prometheus::simpleapi::counter_family_t network_packets + { "zt_network_packets", "number of incoming/outgoing packets per network" }; + +#ifndef ZT_NO_PEER_METRICS + // PeerMetrics + prometheus::CustomFamily> &peer_latency = + prometheus::Builder>() + .Name("zt_peer_latency") + .Help("peer latency (ms)") + .Register(prometheus::simpleapi::registry); + + prometheus::simpleapi::gauge_family_t peer_path_count + { "zt_peer_path_count", "number of paths to peer" }; + prometheus::simpleapi::counter_family_t peer_packets + { "zt_peer_packets", "number of packets to/from a peer" }; + prometheus::simpleapi::counter_family_t peer_packet_errors + { "zt_peer_packet_errors" , "number of incoming packet errors from a peer" }; +#endif // General Controller Metrics prometheus::simpleapi::gauge_metric_t network_count @@ -108,6 +206,34 @@ namespace ZeroTier { prometheus::simpleapi::counter_metric_t member_deauths {"controller_member_deauth_count", "number of network member deauths"}; + prometheus::simpleapi::gauge_metric_t network_config_request_queue_size + { "controller_network_config_request_queue", "number of entries in the request queue for network configurations" }; + + prometheus::simpleapi::counter_metric_t sso_expiration_checks + { "controller_sso_expiration_checks", "number of sso expiration checks done" }; + + prometheus::simpleapi::counter_metric_t sso_member_deauth + { "controller_sso_timeouts", "number of sso timeouts" }; + + prometheus::simpleapi::counter_metric_t network_config_request + { "controller_network_config_request", "count of config requests handled" }; + prometheus::simpleapi::gauge_metric_t network_config_request_threads + { "controller_network_config_request_threads", "number of active network config handling threads" }; + prometheus::simpleapi::counter_metric_t db_get_network + { "controller_db_get_network", "counter" }; + prometheus::simpleapi::counter_metric_t db_get_network_and_member + { "controller_db_get_network_and_member", "counter" }; + prometheus::simpleapi::counter_metric_t db_get_network_and_member_and_summary + { "controller_db_get_networK_and_member_summary", "counter" }; + prometheus::simpleapi::counter_metric_t db_get_member_list + { "controller_db_get_member_list", "counter" }; + prometheus::simpleapi::counter_metric_t db_get_network_list + { "controller_db_get_network_list", "counter" }; + prometheus::simpleapi::counter_metric_t db_member_change + { "controller_db_member_change", "counter" }; + prometheus::simpleapi::counter_metric_t db_network_change + { "controller_db_network_change", "counter" }; + #ifdef ZT_CONTROLLER_USE_LIBPQ // Central Controller Metrics prometheus::simpleapi::counter_metric_t pgsql_mem_notification @@ -116,6 +242,11 @@ namespace ZeroTier { { "controller_pgsql_network_notifications_received", "number of network change notifications received via pgsql NOTIFY" }; prometheus::simpleapi::counter_metric_t pgsql_node_checkin { "controller_pgsql_node_checkin_count", "number of node check-ins (pgsql)" }; + prometheus::simpleapi::counter_metric_t pgsql_commit_ticks + { "controller_pgsql_commit_ticks", "number of commit ticks run (pgsql)" }; + prometheus::simpleapi::counter_metric_t db_get_sso_info + { "controller_db_get_sso_info", "counter" }; + prometheus::simpleapi::counter_metric_t redis_mem_notification { "controller_redis_member_notifications_received", "number of member change notifications received via redis" }; prometheus::simpleapi::counter_metric_t redis_net_notification diff --git a/node/Metrics.hpp b/node/Metrics.hpp index 3b65ea4b9..055288f68 100644 --- a/node/Metrics.hpp +++ b/node/Metrics.hpp @@ -13,6 +13,7 @@ #define METRICS_H_ #include +#include namespace prometheus { namespace simpleapi { @@ -24,44 +25,96 @@ namespace ZeroTier { namespace Metrics { // Packet Type Counts extern prometheus::simpleapi::counter_family_t packets; - extern prometheus::simpleapi::counter_metric_t pkt_error; - extern prometheus::simpleapi::counter_metric_t pkt_ack; - extern prometheus::simpleapi::counter_metric_t pkt_qos; - extern prometheus::simpleapi::counter_metric_t pkt_hello; - extern prometheus::simpleapi::counter_metric_t pkt_ok; - extern prometheus::simpleapi::counter_metric_t pkt_whois; - extern prometheus::simpleapi::counter_metric_t pkt_rendezvous; - extern prometheus::simpleapi::counter_metric_t pkt_frame; - extern prometheus::simpleapi::counter_metric_t pkt_ext_frame; - extern prometheus::simpleapi::counter_metric_t pkt_echo; - extern prometheus::simpleapi::counter_metric_t pkt_multicast_like; - extern prometheus::simpleapi::counter_metric_t pkt_network_credentials; - extern prometheus::simpleapi::counter_metric_t pkt_network_config_request; - extern prometheus::simpleapi::counter_metric_t pkt_network_config; - extern prometheus::simpleapi::counter_metric_t pkt_multicast_gather; - extern prometheus::simpleapi::counter_metric_t pkt_multicast_frame; - extern prometheus::simpleapi::counter_metric_t pkt_push_direct_paths; - extern prometheus::simpleapi::counter_metric_t pkt_user_message; - extern prometheus::simpleapi::counter_metric_t pkt_remote_trace; - extern prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request; + + // incoming packets + extern prometheus::simpleapi::counter_metric_t pkt_nop_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_in; + extern prometheus::simpleapi::counter_metric_t pkt_ack_in; + extern prometheus::simpleapi::counter_metric_t pkt_qos_in; + extern prometheus::simpleapi::counter_metric_t pkt_hello_in; + extern prometheus::simpleapi::counter_metric_t pkt_ok_in; + extern prometheus::simpleapi::counter_metric_t pkt_whois_in; + extern prometheus::simpleapi::counter_metric_t pkt_rendezvous_in; + extern prometheus::simpleapi::counter_metric_t pkt_frame_in; + extern prometheus::simpleapi::counter_metric_t pkt_ext_frame_in; + extern prometheus::simpleapi::counter_metric_t pkt_echo_in; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_like_in; + extern prometheus::simpleapi::counter_metric_t pkt_network_credentials_in; + extern prometheus::simpleapi::counter_metric_t pkt_network_config_request_in; + extern prometheus::simpleapi::counter_metric_t pkt_network_config_in; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_gather_in; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_frame_in; + extern prometheus::simpleapi::counter_metric_t pkt_push_direct_paths_in; + extern prometheus::simpleapi::counter_metric_t pkt_user_message_in; + extern prometheus::simpleapi::counter_metric_t pkt_remote_trace_in; + extern prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request_in; + + // outgoing packets + extern prometheus::simpleapi::counter_metric_t pkt_nop_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_out; + extern prometheus::simpleapi::counter_metric_t pkt_ack_out; + extern prometheus::simpleapi::counter_metric_t pkt_qos_out; + extern prometheus::simpleapi::counter_metric_t pkt_hello_out; + extern prometheus::simpleapi::counter_metric_t pkt_ok_out; + extern prometheus::simpleapi::counter_metric_t pkt_whois_out; + extern prometheus::simpleapi::counter_metric_t pkt_rendezvous_out; + extern prometheus::simpleapi::counter_metric_t pkt_frame_out; + extern prometheus::simpleapi::counter_metric_t pkt_ext_frame_out; + extern prometheus::simpleapi::counter_metric_t pkt_echo_out; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_like_out; + extern prometheus::simpleapi::counter_metric_t pkt_network_credentials_out; + extern prometheus::simpleapi::counter_metric_t pkt_network_config_request_out; + extern prometheus::simpleapi::counter_metric_t pkt_network_config_out; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_gather_out; + extern prometheus::simpleapi::counter_metric_t pkt_multicast_frame_out; + extern prometheus::simpleapi::counter_metric_t pkt_push_direct_paths_out; + extern prometheus::simpleapi::counter_metric_t pkt_user_message_out; + extern prometheus::simpleapi::counter_metric_t pkt_remote_trace_out; + extern prometheus::simpleapi::counter_metric_t pkt_path_negotiation_request_out; // Packet Error Counts extern prometheus::simpleapi::counter_family_t packet_errors; - extern prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found; - extern prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op; - extern prometheus::simpleapi::counter_metric_t pkt_error_identity_collision; - extern prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert; - extern prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied; - extern prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast; - extern prometheus::simpleapi::counter_metric_t pkt_error_authentication_required; + // incoming errors + extern prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_identity_collision_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_in; + extern prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_in; + + // outgoing errors + extern prometheus::simpleapi::counter_metric_t pkt_error_obj_not_found_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_unsupported_op_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_identity_collision_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_need_membership_cert_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_network_access_denied_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_unwanted_multicast_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_authentication_required_out; + extern prometheus::simpleapi::counter_metric_t pkt_error_internal_server_error_out; // Data Sent/Received Metrics + extern prometheus::simpleapi::counter_family_t data; extern prometheus::simpleapi::counter_metric_t udp_send; extern prometheus::simpleapi::counter_metric_t udp_recv; extern prometheus::simpleapi::counter_metric_t tcp_send; extern prometheus::simpleapi::counter_metric_t tcp_recv; + // Network Metrics + extern prometheus::simpleapi::gauge_metric_t network_num_joined; + extern prometheus::simpleapi::gauge_family_t network_num_multicast_groups; + extern prometheus::simpleapi::counter_family_t network_packets; + +#ifndef ZT_NO_PEER_METRICS + // Peer Metrics + extern prometheus::CustomFamily> &peer_latency; + extern prometheus::simpleapi::gauge_family_t peer_path_count; + extern prometheus::simpleapi::counter_family_t peer_packets; + extern prometheus::simpleapi::counter_family_t peer_packet_errors; +#endif + // General Controller Metrics extern prometheus::simpleapi::gauge_metric_t network_count; extern prometheus::simpleapi::gauge_metric_t member_count; @@ -70,15 +123,35 @@ namespace ZeroTier { extern prometheus::simpleapi::counter_metric_t member_auths; extern prometheus::simpleapi::counter_metric_t member_deauths; + extern prometheus::simpleapi::gauge_metric_t network_config_request_queue_size; + extern prometheus::simpleapi::counter_metric_t sso_expiration_checks; + extern prometheus::simpleapi::counter_metric_t sso_member_deauth; + extern prometheus::simpleapi::counter_metric_t network_config_request; + extern prometheus::simpleapi::gauge_metric_t network_config_request_threads; + + extern prometheus::simpleapi::counter_metric_t db_get_network; + extern prometheus::simpleapi::counter_metric_t db_get_network_and_member; + extern prometheus::simpleapi::counter_metric_t db_get_network_and_member_and_summary; + extern prometheus::simpleapi::counter_metric_t db_get_member_list; + extern prometheus::simpleapi::counter_metric_t db_get_network_list; + extern prometheus::simpleapi::counter_metric_t db_member_change; + extern prometheus::simpleapi::counter_metric_t db_network_change; + + #ifdef ZT_CONTROLLER_USE_LIBPQ // Central Controller Metrics extern prometheus::simpleapi::counter_metric_t pgsql_mem_notification; extern prometheus::simpleapi::counter_metric_t pgsql_net_notification; extern prometheus::simpleapi::counter_metric_t pgsql_node_checkin; + extern prometheus::simpleapi::counter_metric_t pgsql_commit_ticks; + extern prometheus::simpleapi::counter_metric_t db_get_sso_info; + extern prometheus::simpleapi::counter_metric_t redis_mem_notification; extern prometheus::simpleapi::counter_metric_t redis_net_notification; extern prometheus::simpleapi::counter_metric_t redis_node_checkin; + + // Central DB Pool Metrics extern prometheus::simpleapi::counter_metric_t conn_counter; extern prometheus::simpleapi::counter_metric_t max_pool_size; diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp index 2a68d0ab9..e9ab9c07f 100644 --- a/node/MulticastGroup.hpp +++ b/node/MulticastGroup.hpp @@ -92,10 +92,11 @@ public: inline bool operator!=(const MulticastGroup &g) const { return ((_mac != g._mac)||(_adi != g._adi)); } inline bool operator<(const MulticastGroup &g) const { - if (_mac < g._mac) + if (_mac < g._mac) { return true; - else if (_mac == g._mac) + } else if (_mac == g._mac) { return (_adi < g._adi); + } return false; } inline bool operator>(const MulticastGroup &g) const { return (g < *this); } diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index d93b2ae10..1306a53f2 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -69,10 +69,11 @@ unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const unsigned int added = 0,i,k,rptr,totalKnown = 0; uint64_t a,picked[(ZT_PROTO_MAX_PACKET_LENGTH / 5) + 2]; - if (!limit) + if (!limit) { return 0; - else if (limit > 0xffff) + } else if (limit > 0xffff) { limit = 0xffff; + } const unsigned int totalAt = appendTo.size(); appendTo.addSize(4); // sizeof(uint32_t) @@ -133,12 +134,14 @@ std::vector
Multicaster::getMembers(uint64_t nwid,const MulticastGroup std::vector
ls; Mutex::Lock _l(_groups_m); const MulticastGroupStatus *s = _groups.get(Multicaster::Key(nwid,mg)); - if (!s) + if (!s) { return ls; + } for(std::vector::const_reverse_iterator m(s->members.rbegin());m!=s->members.rend();++m) { ls.push_back(m->address); - if (ls.size() >= limit) + if (ls.size() >= limit) { break; + } } return ls; } @@ -193,8 +196,11 @@ void Multicaster::send( outp.append((uint32_t)mg.adi()); outp.append((uint16_t)etherType); outp.append(data,len); - if (!network->config().disableCompression()) outp.compress(); + if (!network->config().disableCompression()) { + outp.compress(); + } outp.armor(bestMulticastReplicator->key(),true,bestMulticastReplicator->aesKeysIfSupported()); + Metrics::pkt_multicast_frame_out++; bestMulticastReplicatorPath->send(RR,tPtr,outp.data(),outp.size(),now); return; } @@ -208,12 +214,14 @@ void Multicaster::send( if (!gs.members.empty()) { // Allocate a memory buffer if group is monstrous - if (gs.members.size() > (sizeof(idxbuf) / sizeof(unsigned long))) + if (gs.members.size() > (sizeof(idxbuf) / sizeof(unsigned long))) { indexes = new unsigned long[gs.members.size()]; + } // Generate a random permutation of member indexes - for(unsigned long i=0;i0;--i) { unsigned long j = (unsigned long)RR->node->prng() % (i + 1); unsigned long tmp = indexes[j]; @@ -248,8 +256,9 @@ void Multicaster::send( for(unsigned int i=0;iidentity.address())&&(activeBridges[i] != origin)) { out.sendOnly(RR,tPtr,activeBridges[i]); // optimization: don't use dedup log if it's a one-pass send - if (++count >= limit) + if (++count >= limit) { break; + } } } @@ -276,16 +285,18 @@ void Multicaster::send( unsigned int numExplicitGatherPeers = 0; SharedPtr bestRoot(RR->topology->getUpstreamPeer()); - if (bestRoot) + if (bestRoot) { explicitGatherPeers[numExplicitGatherPeers++] = bestRoot->address(); + } explicitGatherPeers[numExplicitGatherPeers++] = network->controller(); Address ac[ZT_MAX_NETWORK_SPECIALISTS]; const unsigned int accnt = network->config().alwaysContactAddresses(ac); unsigned int shuffled[ZT_MAX_NETWORK_SPECIALISTS]; - for(unsigned int i=0;i>1;inode->prng(); const unsigned int x1 = shuffled[(unsigned int)x % accnt]; @@ -296,16 +307,18 @@ void Multicaster::send( } for(unsigned int i=0;i anchors(network->config().anchors()); for(std::vector
::const_iterator a(anchors.begin());a!=anchors.end();++a) { if (*a != RR->identity.address()) { explicitGatherPeers[numExplicitGatherPeers++] = *a; - if (numExplicitGatherPeers == 16) + if (numExplicitGatherPeers == 16) { break; + } } } @@ -317,10 +330,12 @@ void Multicaster::send( mg.mac().appendTo(outp); outp.append((uint32_t)mg.adi()); outp.append((uint32_t)gatherLimit); - if (com) + if (com) { com->serialize(outp); + } RR->node->expectReplyTo(outp.packetId()); RR->sw->send(tPtr,outp,true); + Metrics::pkt_multicast_gather_out++; } } @@ -340,16 +355,18 @@ void Multicaster::send( data, len); - if (origin) + if (origin) { out.logAsSent(origin); + } unsigned int count = 0; for(unsigned int i=0;iidentity.address()) { out.sendAndLog(RR,tPtr,activeBridges[i]); - if (++count >= limit) + if (++count >= limit) { break; + } } } @@ -365,8 +382,9 @@ void Multicaster::send( } catch ( ... ) {} // this is a sanity check to catch any failures and make sure indexes[] still gets deleted // Free allocated memory buffer if any - if (indexes != idxbuf) + if (indexes != idxbuf) { delete [] indexes; + } } void Multicaster::clean(int64_t now) @@ -377,9 +395,11 @@ void Multicaster::clean(int64_t now) Hashtable::Iterator mm(_groups); while (mm.next(k,s)) { for(std::list::iterator tx(s->txQueue.begin());tx!=s->txQueue.end();) { - if ((tx->expired(now))||(tx->atLimit())) + if ((tx->expired(now))||(tx->atLimit())) { s->txQueue.erase(tx++); - else ++tx; + } else { + ++tx; + } } unsigned long count = 0; @@ -411,8 +431,9 @@ void Multicaster::_add(void *tPtr,int64_t now,uint64_t nwid,const MulticastGroup // assumes _groups_m is locked // Do not add self -- even if someone else returns it - if (member == RR->identity.address()) + if (member == RR->identity.address()) { return; + } std::vector::iterator m(std::lower_bound(gs.members.begin(),gs.members.end(),member)); if (m != gs.members.end()) { @@ -426,13 +447,15 @@ void Multicaster::_add(void *tPtr,int64_t now,uint64_t nwid,const MulticastGroup } for(std::list::iterator tx(gs.txQueue.begin());tx!=gs.txQueue.end();) { - if (tx->atLimit()) + if (tx->atLimit()) { gs.txQueue.erase(tx++); - else { + } else { tx->sendIfNew(RR,tPtr,member); - if (tx->atLimit()) + if (tx->atLimit()) { gs.txQueue.erase(tx++); - else ++tx; + } else { + ++tx; + } } } } diff --git a/node/Network.cpp b/node/Network.cpp index b03f4b3d0..1e77e4636 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -32,6 +32,7 @@ #include "Node.hpp" #include "Peer.hpp" #include "Trace.hpp" +#include "Metrics.hpp" #include @@ -42,8 +43,9 @@ namespace { // Returns true if packet appears valid; pos and proto will be set static inline bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) { - if (frameLen < 40) + if (frameLen < 40) { return false; + } pos = 40; proto = frameData[6]; while (pos <= frameLen) { @@ -52,8 +54,9 @@ static inline bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLe case 43: // routing case 60: // destination options case 135: // mobility options - if ((pos + 8) > frameLen) + if ((pos + 8) > frameLen) { return false; // invalid! + } proto = frameData[pos]; pos += ((unsigned int)frameData[pos + 1] * 8) + 8; break; @@ -165,8 +168,9 @@ static _doZtFilterResult _doZtFilter( case ZT_NETWORK_RULE_ACTION_TEE: case ZT_NETWORK_RULE_ACTION_WATCH: case ZT_NETWORK_RULE_ACTION_REDIRECT: - if (RR->identity.address() == rules[rn].v.fwd.address) + if (RR->identity.address() == rules[rn].v.fwd.address) { superAccept = true; + } break; default: break; @@ -342,7 +346,9 @@ static _doZtFilterResult _doZtFilter( case 0x84: // SCTP case 0x88: // UDPLite if (frameLen > (pos + 4)) { - if (rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) pos += 2; + if (rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) { + pos += 2; + } p = (int)frameData[pos++] << 8; p |= (int)frameData[pos]; } @@ -358,8 +364,12 @@ static _doZtFilterResult _doZtFilter( break; case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: { uint64_t cf = (inbound) ? ZT_RULE_PACKET_CHARACTERISTICS_INBOUND : 0ULL; - if (macDest.isMulticast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST; - if (macDest.isBroadcast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST; + if (macDest.isMulticast()) { + cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST; + } + if (macDest.isBroadcast()) { + cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST; + } if (ownershipVerificationMask == 1) { ownershipVerificationMask = 0; InetAddress src; @@ -386,17 +396,21 @@ static _doZtFilterResult _doZtFilter( } if (inbound) { if (membership) { - if ((src)&&(membership->hasCertificateOfOwnershipFor(nconf,src))) + if ((src)&&(membership->hasCertificateOfOwnershipFor(nconf,src))) { ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED; - if (membership->hasCertificateOfOwnershipFor(nconf,macSource)) + } + if (membership->hasCertificateOfOwnershipFor(nconf,macSource)) { ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED; + } } } else { for(unsigned int i=0;i> 7) & 1)); - else thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1)); + } else { + thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1)); + } } return DOZTFILTER_NO_MATCH; @@ -544,23 +560,31 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u RR(renv), _uPtr(uptr), _id(nwid), + _nwidStr(OSUtils::networkIDStr(nwid)), _lastAnnouncedMulticastGroupsUpstream(0), _mac(renv->identity.address(),nwid), _portInitialized(false), _lastConfigUpdate(0), _destroyed(false), _netconfFailure(NETCONF_FAILURE_NONE), - _portError(0) + _portError(0), + _num_multicast_groups{Metrics::network_num_multicast_groups.Add({{"network_id", _nwidStr}})}, + _incoming_packets_accepted{Metrics::network_packets.Add({{"direction","rx"},{"network_id", _nwidStr},{"accepted","yes"}})}, + _incoming_packets_dropped{Metrics::network_packets.Add({{"direction","rx"},{"network_id", _nwidStr},{"accepted","no"}})}, + _outgoing_packets_accepted{Metrics::network_packets.Add({{"direction","tx"},{"network_id", _nwidStr},{"accepted","yes"}})}, + _outgoing_packets_dropped{Metrics::network_packets.Add({{"direction","tx"},{"network_id", _nwidStr},{"accepted","no"}})} { - for(int i=0;isetConfiguration(tPtr,*nconf,false); _lastConfigUpdate = 0; // still want to re-request since it's likely outdated } else { uint64_t tmp[2]; - tmp[0] = nwid; tmp[1] = 0; + tmp[0] = nwid; + tmp[1] = 0; bool got = false; Dictionary *dict = new Dictionary(); @@ -580,8 +604,9 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u } catch ( ... ) {} delete dict; - if (!got) + if (!got) { RR->node->stateObjectPut(tPtr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp,"\n",1); + } } if (!_portInitialized) { @@ -591,13 +616,15 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u _portError = RR->node->configureVirtualNetworkPort(tPtr,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); _portInitialized = true; } + + Metrics::network_num_joined++; } Network::~Network() { ZT_VirtualNetworkConfig ctmp; _externalConfig(&ctmp); - + Metrics::network_num_joined--; if (_destroyed) { // This is done in Node::leave() so we can pass tPtr properly //RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); @@ -664,14 +691,16 @@ bool Network::filterOutgoingPacket( break; } - if (accept) + if (accept) { break; + } } } break; case DOZTFILTER_DROP: - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(Trace::RuleResultLog *)0,(Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,0); + } return false; case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter() @@ -685,6 +714,7 @@ bool Network::filterOutgoingPacket( } if (accept) { + _outgoing_packets_accepted++; if ((!noTee)&&(cc)) { Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME); outp.append(_id); @@ -708,17 +738,21 @@ bool Network::filterOutgoingPacket( outp.compress(); RR->sw->send(tPtr,outp,true); - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog *)0,(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,0); + } return false; // DROP locally, since we redirected } else { - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog *)0,(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,1); + } return true; } } else { - if (_config.remoteTraceTarget) + _outgoing_packets_dropped++; + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog *)0,(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability *)0,ztSource,ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,noTee,false,0); + } return false; } } @@ -788,8 +822,9 @@ int Network::filterIncomingPacket( } break; case DOZTFILTER_DROP: - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(Trace::RuleResultLog *)0,(Capability *)0,sourcePeer->address(),ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,false,true,0); + } return 0; // DROP case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter() @@ -802,6 +837,7 @@ int Network::filterIncomingPacket( } if (accept) { + _incoming_packets_accepted++; if (cc) { Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME); outp.append(_id); @@ -825,24 +861,29 @@ int Network::filterIncomingPacket( outp.compress(); RR->sw->send(tPtr,outp,true); - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(c) ? &crrl : (Trace::RuleResultLog *)0,c,sourcePeer->address(),ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,false,true,0); + } return 0; // DROP locally, since we redirected } + } else { + _incoming_packets_dropped++; } - if (_config.remoteTraceTarget) + if (_config.remoteTraceTarget) { RR->t->networkFilter(tPtr,*this,rrl,(c) ? &crrl : (Trace::RuleResultLog *)0,c,sourcePeer->address(),ztDest,macSource,macDest,frameData,frameLen,etherType,vlanId,false,true,accept); + } return accept; } bool Network::subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBridgedGroups) const { Mutex::Lock _l(_lock); - if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) + if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) { return true; - else if (includeBridgedGroups) + } else if (includeBridgedGroups) { return _multicastGroupsBehindMe.contains(mg); + } return false; } @@ -852,6 +893,7 @@ void Network::multicastSubscribe(void *tPtr,const MulticastGroup &mg) if (!std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) { _myMulticastGroups.insert(std::upper_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg),mg); _sendUpdatesToMembers(tPtr,&mg); + _num_multicast_groups++; } } @@ -859,20 +901,25 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg) { Mutex::Lock _l(_lock); std::vector::iterator i(std::lower_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)); - if ( (i != _myMulticastGroups.end()) && (*i == mg) ) + if ( (i != _myMulticastGroups.end()) && (*i == mg) ) { _myMulticastGroups.erase(i); + _num_multicast_groups--; + } } uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) { - if (_destroyed) + if (_destroyed) { return 0; + } const unsigned int start = ptr; ptr += 8; // skip network ID, which is already obviously known - const unsigned int chunkLen = chunk.at(ptr); ptr += 2; - const void *chunkData = chunk.field(ptr,chunkLen); ptr += chunkLen; + const unsigned int chunkLen = chunk.at(ptr); + ptr += 2; + const void *chunkData = chunk.field(ptr,chunkLen); + ptr += chunkLen; NetworkConfig *nc = (NetworkConfig *)0; uint64_t configUpdateId; @@ -884,19 +931,25 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add unsigned long totalLength,chunkIndex; if (ptr < chunk.size()) { const bool fastPropagate = ((chunk[ptr++] & 0x01) != 0); - configUpdateId = chunk.at(ptr); ptr += 8; - totalLength = chunk.at(ptr); ptr += 4; - chunkIndex = chunk.at(ptr); ptr += 4; + configUpdateId = chunk.at(ptr); + ptr += 8; + totalLength = chunk.at(ptr); + ptr += 4; + chunkIndex = chunk.at(ptr); + ptr += 4; - if (((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) // >= since we need room for a null at the end + if (((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) { // >= since we need room for a null at the end return 0; - if ((chunk[ptr] != 1)||(chunk.at(ptr + 1) != ZT_C25519_SIGNATURE_LEN)) + } + if ((chunk[ptr] != 1)||(chunk.at(ptr + 1) != ZT_C25519_SIGNATURE_LEN)) { return 0; + } const uint8_t *sig = reinterpret_cast(chunk.field(ptr + 3,ZT_C25519_SIGNATURE_LEN)); // We can use the signature, which is unique per chunk, to get a per-chunk ID for local deduplication use - for(unsigned int i=0;i<16;++i) + for(unsigned int i=0;i<16;++i) { reinterpret_cast(&chunkId)[i & 7] ^= sig[i]; + } // Find existing or new slot for this update and check if this is a duplicate chunk for(int i=0;ihaveChunks;++j) { - if (c->haveChunkIds[j] == chunkId) + if (c->haveChunkIds[j] == chunkId) { return 0; + } } break; @@ -916,10 +970,12 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add // If it's not a duplicate, check chunk signature const Identity controllerId(RR->topology->getIdentity(tPtr,controller())); - if (!controllerId) // we should always have the controller identity by now, otherwise how would we have queried it the first time? + if (!controllerId) { // we should always have the controller identity by now, otherwise how would we have queried it the first time? return 0; - if (!controllerId.verify(chunk.field(start,ptr - start),ptr - start,sig,ZT_C25519_SIGNATURE_LEN)) + } + if (!controllerId.verify(chunk.field(start,ptr - start),ptr - start,sig,ZT_C25519_SIGNATURE_LEN)) { return 0; + } // New properly verified chunks can be flooded "virally" through the network if (fastPropagate) { @@ -941,12 +997,14 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add totalLength = chunkLen; chunkIndex = 0; - if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY) + if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY) { return 0; + } for(int i=0;its)) + if ((!c)||(_incomingConfigChunks[i].ts < c->ts)) { c = &(_incomingConfigChunks[i]); + } } } else { // Single-chunk unsigned legacy configs are only allowed from the controller itself @@ -960,8 +1018,9 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add c->haveChunks = 0; c->haveBytes = 0; } - if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS) + if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS) { return false; + } c->haveChunkIds[c->haveChunks++] = chunkId; memcpy(c->data.unsafeData() + chunkIndex,chunkData,chunkLen); @@ -996,15 +1055,18 @@ uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Add int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToDisk) { - if (_destroyed) + if (_destroyed) { return 0; + } // _lock is NOT locked when this is called try { - if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) + if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) { return 0; // invalid config that is not for us or not for this network - if (_config == nconf) + } + if (_config == nconf) { return 1; // OK config, but duplicate of what we already have + } ZT_VirtualNetworkConfig ctmp; bool oldPortInitialized; @@ -1029,7 +1091,8 @@ int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToD try { if (nconf.toDictionary(*d,false)) { uint64_t tmp[2]; - tmp[0] = _id; tmp[1] = 0; + tmp[0] = _id; + tmp[1] = 0; RR->node->stateObjectPut(tPtr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp,d->data(),d->sizeBytes()); } } catch ( ... ) {} @@ -1043,8 +1106,9 @@ int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToD void Network::requestConfiguration(void *tPtr) { - if (_destroyed) + if (_destroyed) { return; + } if ((_id >> 56) == 0xff) { if ((_id & 0xffffff) == 0) { @@ -1145,8 +1209,9 @@ void Network::requestConfiguration(void *tPtr) nconf->staticIpCount = 2; nconf->ruleCount = 1; - if (networkHub != 0) + if (networkHub != 0) { nconf->specialists[0] = networkHub; + } nconf->staticIps[0] = InetAddress::makeIpv66plane(_id,myAddress); nconf->staticIps[1].set(ipv4,4,8); @@ -1162,7 +1227,9 @@ void Network::requestConfiguration(void *tPtr) nconf->name[4] = 'c'; nconf->name[5] = '-'; unsigned long nn = 6; - while ((nconf->name[nn] = v4ascii[nn - 6])) ++nn; + while ((nconf->name[nn] = v4ascii[nn - 6])) { + ++nn; + } nconf->name[nn++] = '.'; nconf->name[nn++] = '0'; nconf->name[nn++] = '.'; @@ -1234,8 +1301,9 @@ bool Network::gate(void *tPtr,const SharedPtr &peer) // comRevocationThreshold = m->comRevocationThreshold(); //} if ( (_config.isPublic()) || ((m)&&(m->isAllowedOnNetwork(_config, peer->identity()))) ) { - if (!m) + if (!m) { m = &(_membership(peer->address())); + } if (m->multicastLikeGate(now)) { _announceMulticastGroupsTo(tPtr,peer->address(),_allMulticastGroups()); } @@ -1260,16 +1328,18 @@ void Network::clean() const int64_t now = RR->node->now(); Mutex::Lock _l(_lock); - if (_destroyed) + if (_destroyed) { return; + } { Hashtable< MulticastGroup,uint64_t >::Iterator i(_multicastGroupsBehindMe); MulticastGroup *mg = (MulticastGroup *)0; uint64_t *ts = (uint64_t *)0; while (i.next(mg,ts)) { - if ((now - *ts) > (ZT_MULTICAST_LIKE_EXPIRE * 2)) + if ((now - *ts) > (ZT_MULTICAST_LIKE_EXPIRE * 2)) { _multicastGroupsBehindMe.erase(*mg); + } } } @@ -1278,9 +1348,11 @@ void Network::clean() Membership *m = (Membership *)0; Hashtable::Iterator i(_memberships); while (i.next(a,m)) { - if (!RR->topology->getPeerNoCache(*a)) + if (!RR->topology->getPeerNoCache(*a)) { _memberships.erase(*a); - else m->clean(now,_config); + } else { + m->clean(now,_config); + } } } } @@ -1315,8 +1387,9 @@ void Network::learnBridgeRoute(const MAC &mac,const Address &addr) { Hashtable::Iterator i(_remoteBridgeRoutes); while (i.next(m,a)) { - if (*a == maxAddr) + if (*a == maxAddr) { _remoteBridgeRoutes.erase(*m); + } } } } @@ -1327,22 +1400,25 @@ void Network::learnBridgedMulticastGroup(void *tPtr,const MulticastGroup &mg,int Mutex::Lock _l(_lock); const unsigned long tmp = (unsigned long)_multicastGroupsBehindMe.size(); _multicastGroupsBehindMe.set(mg,now); - if (tmp != _multicastGroupsBehindMe.size()) + if (tmp != _multicastGroupsBehindMe.size()) { _sendUpdatesToMembers(tPtr,&mg); + } } Membership::AddCredentialResult Network::addCredential(void *tPtr,const CertificateOfMembership &com) { - if (com.networkId() != _id) + if (com.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); return _membership(com.issuedTo()).addCredential(RR,tPtr,_config,com); } Membership::AddCredentialResult Network::addCredential(void *tPtr,const Address &sentFrom,const Revocation &rev) { - if (rev.networkId() != _id) + if (rev.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); Membership &m = _membership(rev.target()); @@ -1379,8 +1455,9 @@ void Network::destroy() ZT_VirtualNetworkStatus Network::_status() const { // assumes _lock is locked - if (_portError) + if (_portError) { return ZT_NETWORK_STATUS_PORT_ERROR; + } switch(_netconfFailure) { case NETCONF_FAILURE_ACCESS_DENIED: return ZT_NETWORK_STATUS_ACCESS_DENIED; @@ -1400,9 +1477,11 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const // assumes _lock is locked ec->nwid = _id; ec->mac = _mac.toInt(); - if (_config) + if (_config) { Utils::scopy(ec->name,sizeof(ec->name),_config.name); - else ec->name[0] = (char)0; + } else { + ec->name[0] = (char)0; + } ec->status = _status(); ec->type = (_config) ? (_config.isPrivate() ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC) : ZT_NETWORK_TYPE_PRIVATE; ec->mtu = (_config) ? _config.mtu : ZT_DEFAULT_MTU; @@ -1459,23 +1538,28 @@ void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMu const int64_t now = RR->node->now(); std::vector groups; - if (newMulticastGroup) + if (newMulticastGroup) { groups.push_back(*newMulticastGroup); - else groups = _allMulticastGroups(); + } else { + groups = _allMulticastGroups(); + } std::vector
alwaysAnnounceTo; if ((newMulticastGroup)||((now - _lastAnnouncedMulticastGroupsUpstream) >= ZT_MULTICAST_ANNOUNCE_PERIOD)) { - if (!newMulticastGroup) + if (!newMulticastGroup) { _lastAnnouncedMulticastGroupsUpstream = now; + } alwaysAnnounceTo = _config.alwaysContactAddresses(); - if (std::find(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),controller()) == alwaysAnnounceTo.end()) + if (std::find(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),controller()) == alwaysAnnounceTo.end()) { alwaysAnnounceTo.push_back(controller()); + } const std::vector
upstreams(RR->topology->upstreamAddresses()); for(std::vector
::const_iterator a(upstreams.begin());a!=upstreams.end();++a) { - if (std::find(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),*a) == alwaysAnnounceTo.end()) + if (std::find(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),*a) == alwaysAnnounceTo.end()) { alwaysAnnounceTo.push_back(*a); + } } std::sort(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end()); @@ -1504,8 +1588,9 @@ void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMu while (i.next(a,m)) { const Identity remoteIdentity(RR->topology->getIdentity(tPtr, *a)); if (remoteIdentity) { - if ( ( m->multicastLikeGate(now) || (newMulticastGroup) ) && (m->isAllowedOnNetwork(_config, remoteIdentity)) && (!std::binary_search(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),*a)) ) + if ( ( m->multicastLikeGate(now) || (newMulticastGroup) ) && (m->isAllowedOnNetwork(_config, remoteIdentity)) && (!std::binary_search(alwaysAnnounceTo.begin(),alwaysAnnounceTo.end(),*a)) ) { _announceMulticastGroupsTo(tPtr,*a,groups); + } } } } @@ -1544,8 +1629,9 @@ std::vector Network::_allMulticastGroups() const mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1); mgs.insert(mgs.end(),_myMulticastGroups.begin(),_myMulticastGroups.end()); _multicastGroupsBehindMe.appendKeys(mgs); - if ((_config)&&(_config.enableBroadcast())) + if ((_config)&&(_config.enableBroadcast())) { mgs.push_back(Network::BROADCAST); + } std::sort(mgs.begin(),mgs.end()); mgs.erase(std::unique(mgs.begin(),mgs.end()),mgs.end()); return mgs; diff --git a/node/Network.hpp b/node/Network.hpp index 275e82f02..a3bce14af 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -37,6 +37,7 @@ #include "Membership.hpp" #include "NetworkConfig.hpp" #include "CertificateOfMembership.hpp" +#include "Metrics.hpp" #define ZT_NETWORK_MAX_INCOMING_UPDATES 3 #define ZT_NETWORK_MAX_UPDATE_CHUNKS ((ZT_NETWORKCONFIG_DICT_CAPACITY / 1024) + 1) @@ -332,8 +333,9 @@ public: */ inline Membership::AddCredentialResult addCredential(void *tPtr,const Capability &cap) { - if (cap.networkId() != _id) + if (cap.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); return _membership(cap.issuedTo()).addCredential(RR,tPtr,_config,cap); } @@ -343,8 +345,9 @@ public: */ inline Membership::AddCredentialResult addCredential(void *tPtr,const Tag &tag) { - if (tag.networkId() != _id) + if (tag.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); return _membership(tag.issuedTo()).addCredential(RR,tPtr,_config,tag); } @@ -359,8 +362,9 @@ public: */ inline Membership::AddCredentialResult addCredential(void *tPtr,const CertificateOfOwnership &coo) { - if (coo.networkId() != _id) + if (coo.networkId() != _id) { return Membership::ADD_REJECTED; + } Mutex::Lock _l(_lock); return _membership(coo.issuedTo()).addCredential(RR,tPtr,_config,coo); } @@ -377,8 +381,9 @@ public: Mutex::Lock _l(_lock); Membership &m = _membership(to); const int64_t lastPushed = m.lastPushedCredentials(); - if ((lastPushed < _lastConfigUpdate)||((now - lastPushed) > ZT_PEER_CREDENTIALS_REQUEST_RATE_LIMIT)) + if ((lastPushed < _lastConfigUpdate)||((now - lastPushed) > ZT_PEER_CREDENTIALS_REQUEST_RATE_LIMIT)) { m.pushCredentials(RR,tPtr,now,to,_config); + } } /** @@ -393,8 +398,9 @@ public: Mutex::Lock _l(_lock); Membership &m = _membership(to); const int64_t lastPushed = m.lastPushedCredentials(); - if ((lastPushed < _lastConfigUpdate)||((now - lastPushed) > ZT_PEER_ACTIVITY_TIMEOUT)) + if ((lastPushed < _lastConfigUpdate)||((now - lastPushed) > ZT_PEER_ACTIVITY_TIMEOUT)) { m.pushCredentials(RR,tPtr,now,to,_config); + } } /** @@ -434,6 +440,7 @@ private: const RuntimeEnvironment *const RR; void *_uPtr; const uint64_t _id; + std::string _nwidStr; uint64_t _lastAnnouncedMulticastGroupsUpstream; MAC _mac; // local MAC address bool _portInitialized; @@ -474,6 +481,12 @@ private: Mutex _lock; AtomicCounter __refCount; + + prometheus::simpleapi::gauge_metric_t _num_multicast_groups; + prometheus::simpleapi::counter_metric_t _incoming_packets_accepted; + prometheus::simpleapi::counter_metric_t _incoming_packets_dropped; + prometheus::simpleapi::counter_metric_t _outgoing_packets_accepted; + prometheus::simpleapi::counter_metric_t _outgoing_packets_dropped; }; } // namespace ZeroTier diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 3dc3b36d6..d2fa844c2 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -29,48 +29,97 @@ bool NetworkConfig::toDictionary(Dictionary &d,b // Try to put the more human-readable fields first - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,this->credentialTimeMaxDelta)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo.toString(tmp2))) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET,this->remoteTraceTarget.toString(tmp2))) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL,(uint64_t)this->remoteTraceLevel)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)this->type)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU,(uint64_t)this->mtu)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,this->credentialTimeMaxDelta)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo.toString(tmp2))) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET,this->remoteTraceTarget.toString(tmp2))) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL,(uint64_t)this->remoteTraceLevel)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)this->type)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name)) { + delete tmp; + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU,(uint64_t)this->mtu)) { + delete tmp; + return false; + } #ifdef ZT_SUPPORT_OLD_STYLE_NETCONF if (includeLegacy) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD,this->enableBroadcast())) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,this->isPrivate())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD,this->enableBroadcast())) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,this->isPrivate())) { + return false; + } std::string v4s; for(unsigned int i=0;istaticIps[i].ss_family == AF_INET) { - if (v4s.length() > 0) + if (v4s.length() > 0) { v4s.push_back(','); + } char buf[64]; v4s.append(this->staticIps[i].toString(buf)); } } if (v4s.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,v4s.c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,v4s.c_str())) { + return false; + } } std::string v6s; for(unsigned int i=0;istaticIps[i].ss_family == AF_INET6) { - if (v6s.length() > 0) + if (v6s.length() > 0) { v6s.push_back(','); + } char buf[64]; v6s.append(this->staticIps[i].toString(buf)); } } if (v6s.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,v6s.c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,v6s.c_str())) { + return false; + } } std::string ets; @@ -82,8 +131,9 @@ bool NetworkConfig::toDictionary(Dictionary &d,b et = rules[i].v.etherType; } else if (rt == ZT_NETWORK_RULE_ACTION_ACCEPT) { if (((int)lastrt < 32)||(lastrt == ZT_NETWORK_RULE_MATCH_ETHERTYPE)) { - if (ets.length() > 0) + if (ets.length() > 0) { ets.push_back(','); + } char tmp2[16] = {0}; ets.append(Utils::hex((uint16_t)et,tmp2)); } @@ -92,24 +142,31 @@ bool NetworkConfig::toDictionary(Dictionary &d,b lastrt = rt; } if (ets.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD,ets.c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD,ets.c_str())) { + return false; + } } if (this->com) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,this->com.toString().c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,this->com.toString().c_str())) { + return false; + } } std::string ab; for(unsigned int i=0;ispecialistCount;++i) { if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) { - if (ab.length() > 0) + if (ab.length() > 0) { ab.push_back(','); + } char tmp2[16] = {0}; ab.append(Address(this->specialists[i]).toString(tmp2)); } } if (ab.length() > 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,ab.c_str())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,ab.c_str())) { + return false; + } } } #endif // ZT_SUPPORT_OLD_STYLE_NETCONF @@ -119,35 +176,49 @@ bool NetworkConfig::toDictionary(Dictionary &d,b if (this->com) { tmp->clear(); this->com.serialize(*tmp); - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;icapabilityCount;++i) + for(unsigned int i=0;icapabilityCount;++i) { this->capabilities[i].serialize(*tmp); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;itagCount;++i) + for(unsigned int i=0;itagCount;++i) { this->tags[i].serialize(*tmp); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;icertificateOfOwnershipCount;++i) + for(unsigned int i=0;icertificateOfOwnershipCount;++i) { this->certificatesOfOwnership[i].serialize(*tmp); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;ispecialistCount;++i) + for(unsigned int i=0;ispecialistCount;++i) { tmp->append((uint64_t)this->specialists[i]); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,*tmp)) { + return false; + } } tmp->clear(); @@ -158,50 +229,83 @@ bool NetworkConfig::toDictionary(Dictionary &d,b tmp->append((uint16_t)this->routes[i].metric); } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,*tmp)) { + return false; + } } tmp->clear(); - for(unsigned int i=0;istaticIpCount;++i) + for(unsigned int i=0;istaticIpCount;++i) { this->staticIps[i].serialize(*tmp); + } if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,*tmp)) { + return false; + } } if (this->ruleCount) { tmp->clear(); Capability::serializeRules(*tmp,rules,ruleCount); if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES,*tmp)) { + return false; + } } } tmp->clear(); DNS::serializeDNS(*tmp, &dns); if (tmp->size()) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) { + return false; + } } if (this->ssoVersion == 0) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) { + return false; + } if (this->ssoEnabled) { if (this->authenticationURL[0]) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) { + return false; + } + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) { + return false; } - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) return false; } } else if(this->ssoVersion == 1) { - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) { + return false; + } //if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID)) return false; - if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_PROVIDER, this->ssoProvider)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID)) { + return false; + } + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_PROVIDER, this->ssoProvider)) { + return false; + } } delete tmp; @@ -241,37 +345,45 @@ bool NetworkConfig::fromDictionary(const Dictionaryname,sizeof(this->name)); this->mtu = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MTU,ZT_DEFAULT_MTU); - if (this->mtu < 1280) + if (this->mtu < 1280) { this->mtu = 1280; // minimum MTU allowed by IPv6 standard and others - else if (this->mtu > ZT_MAX_MTU) + } else if (this->mtu > ZT_MAX_MTU) { this->mtu = ZT_MAX_MTU; + } if (d.getUI(ZT_NETWORKCONFIG_DICT_KEY_VERSION,0) < 6) { #ifdef ZT_SUPPORT_OLD_STYLE_NETCONF char tmp2[1024] = {0}; // Decode legacy fields if version is old - if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) + if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) { this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + } this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; // always enable for old-style netconf this->type = (d.getB(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,true)) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { char *saveptr = (char *)0; for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { - if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) break; + if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + break; + } InetAddress ip(f); - if (!ip.isNetwork()) + if (!ip.isNetwork()) { this->staticIps[this->staticIpCount++] = ip; + } } } if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { char *saveptr = (char *)0; for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { - if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) break; + if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + break; + } InetAddress ip(f); - if (!ip.isNetwork()) + if (!ip.isNetwork()) { this->staticIps[this->staticIpCount++] = ip; + } } } @@ -283,7 +395,9 @@ bool NetworkConfig::fromDictionary(const DictionaryruleCount + 2) > ZT_MAX_NETWORK_RULES) break; + if ((this->ruleCount + 2) > ZT_MAX_NETWORK_RULES) { + break; + } if (et > 0) { this->rules[this->ruleCount].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE; this->rules[this->ruleCount].v.etherType = (uint16_t)et; @@ -311,8 +425,9 @@ bool NetworkConfig::fromDictionary(const Dictionaryflags = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,0); this->type = (ZT_VirtualNetworkType)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)ZT_NETWORK_TYPE_PRIVATE); - if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) { this->com.deserialize(*tmp,0); + } if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) { try { @@ -341,9 +456,9 @@ bool NetworkConfig::fromDictionary(const Dictionarysize()) { - if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP) + if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP) { p += certificatesOfOwnership[certificateOfOwnershipCount++].deserialize(*tmp,p); - else { + } else { CertificateOfOwnership foo; p += foo.deserialize(*tmp,p); } @@ -353,8 +468,9 @@ bool NetworkConfig::fromDictionary(const Dictionarysize()) { - if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) + if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) { this->specialists[this->specialistCount++] = tmp->at(p); + } p += 8; } } @@ -364,8 +480,10 @@ bool NetworkConfig::fromDictionary(const Dictionarysize())&&(routeCount < ZT_MAX_NETWORK_ROUTES)) { p += reinterpret_cast(&(this->routes[this->routeCount].target))->deserialize(*tmp,p); p += reinterpret_cast(&(this->routes[this->routeCount].via))->deserialize(*tmp,p); - this->routes[this->routeCount].flags = tmp->at(p); p += 2; - this->routes[this->routeCount].metric = tmp->at(p); p += 2; + this->routes[this->routeCount].flags = tmp->at(p); + p += 2; + this->routes[this->routeCount].metric = tmp->at(p); + p += 2; ++this->routeCount; } } diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index cd713dde8..859d7212d 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -356,8 +356,9 @@ public: { std::vector
r; for(unsigned int i=0;i r; for(unsigned int i=0;i r; for(unsigned int i=0;i r; for(unsigned int i=0;iversion != 0) + if (callbacks->version != 0) { throw ZT_EXCEPTION_INVALID_ARGUMENT; + } memcpy(&_cb,callbacks,sizeof(ZT_Node_Callbacks)); // Initialize non-cryptographic PRNG from a good random source @@ -68,7 +70,8 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 memset((void *)(&_stats),0,sizeof(_stats)); uint64_t idtmp[2]; - idtmp[0] = 0; idtmp[1] = 0; + idtmp[0] = 0; + idtmp[1] = 0; char tmp[2048]; int n = stateObjectGet(tptr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp,tmp,sizeof(tmp) - 1); if (n > 0) { @@ -85,15 +88,18 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 RR->identity.generate(); RR->identity.toString(false,RR->publicIdentityStr); RR->identity.toString(true,RR->secretIdentityStr); - idtmp[0] = RR->identity.address().toInt(); idtmp[1] = 0; + idtmp[0] = RR->identity.address().toInt(); + idtmp[1] = 0; stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp,RR->secretIdentityStr,(unsigned int)strlen(RR->secretIdentityStr)); stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr,(unsigned int)strlen(RR->publicIdentityStr)); } else { - idtmp[0] = RR->identity.address().toInt(); idtmp[1] = 0; + idtmp[0] = RR->identity.address().toInt(); + idtmp[1] = 0; n = stateObjectGet(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,tmp,sizeof(tmp) - 1); if ((n > 0)&&(n < (int)sizeof(RR->publicIdentityStr))&&(n < (int)sizeof(tmp))) { - if (memcmp(tmp,RR->publicIdentityStr,n)) + if (memcmp(tmp,RR->publicIdentityStr,n)) { stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr,(unsigned int)strlen(RR->publicIdentityStr)); + } } } @@ -107,10 +113,13 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 const unsigned long bc = sizeof(Bond) + (((sizeof(Bond) & 0xf) != 0) ? (16 - (sizeof(Bond) & 0xf)) : 0); m = reinterpret_cast(::malloc(16 + ts + sws + mcs + topologys + sas + bc)); - if (!m) + if (!m) { throw std::bad_alloc(); + } RR->rtmem = m; - while (((uintptr_t)m & 0xf) != 0) ++m; + while (((uintptr_t)m & 0xf) != 0) { + ++m; + } RR->t = new (m) Trace(RR); m += ts; @@ -124,12 +133,24 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,int64 m += sas; RR->bc = new (m) Bond(RR); } catch ( ... ) { - if (RR->sa) RR->sa->~SelfAwareness(); - if (RR->topology) RR->topology->~Topology(); - if (RR->mc) RR->mc->~Multicaster(); - if (RR->sw) RR->sw->~Switch(); - if (RR->t) RR->t->~Trace(); - if (RR->bc) RR->bc->~Bond(); + if (RR->sa) { + RR->sa->~SelfAwareness(); + } + if (RR->topology) { + RR->topology->~Topology(); + } + if (RR->mc) { + RR->mc->~Multicaster(); + } + if (RR->sw) { + RR->sw->~Switch(); + } + if (RR->t) { + RR->t->~Trace(); + } + if (RR->bc) { + RR->bc->~Bond(); + } ::free(m); throw; } @@ -143,12 +164,24 @@ Node::~Node() Mutex::Lock _l(_networks_m); _networks.clear(); // destroy all networks before shutdown } - if (RR->sa) RR->sa->~SelfAwareness(); - if (RR->topology) RR->topology->~Topology(); - if (RR->mc) RR->mc->~Multicaster(); - if (RR->sw) RR->sw->~Switch(); - if (RR->t) RR->t->~Trace(); - if (RR->bc) RR->bc->~Bond(); + if (RR->sa) { + RR->sa->~SelfAwareness(); + } + if (RR->topology) { + RR->topology->~Topology(); + } + if (RR->mc) { + RR->mc->~Multicaster(); + } + if (RR->sw) { + RR->sw->~Switch(); + } + if (RR->t) { + RR->t->~Trace(); + } + if (RR->bc) { + RR->bc->~Bond(); + } ::free(RR->rtmem); } @@ -183,7 +216,9 @@ ZT_ResultCode Node::processVirtualNetworkFrame( if (nw) { RR->sw->onLocalEthernet(tptr,nw,MAC(sourceMac),MAC(destMac),etherType,vlanId,frameData,frameLength); return ZT_RESULT_OK; - } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } else { + return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } } // Closure used to ping upstream and active/online peers @@ -238,8 +273,9 @@ public: if ((!contacted)&&(_bestCurrentUpstream)) { const SharedPtr up(_bestCurrentUpstream->getAppropriatePath(_now,true)); - if (up) + if (up) { p->sendHELLO(_tPtr,up->localSocket(),up->address(),_now); + } } _alwaysContact.erase(p->address()); // after this we'll WHOIS all upstreams that remain @@ -298,8 +334,9 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 std::vector *upstreamStableEndpoints = (std::vector *)0; while (i.next(upstreamAddress,upstreamStableEndpoints)) { SharedPtr p(RR->topology->getPeerNoCache(*upstreamAddress)); - if (p) + if (p) { lastReceivedFromUpstream = std::max(p->lastReceive(),lastReceivedFromUpstream); + } } } @@ -310,8 +347,9 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 _LocalControllerAuth *k = (_LocalControllerAuth *)0; int64_t *v = (int64_t *)0; while (i.next(k,v)) { - if ((*v - now) > (ZT_NETWORK_AUTOCONF_DELAY * 3)) + if ((*v - now) > (ZT_NETWORK_AUTOCONF_DELAY * 3)) { _localControllerAuthorizations.erase(*k); + } } _localControllerAuthorizations_m.unlock(); } @@ -340,8 +378,9 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 Hashtable< Address,std::vector >::Iterator i(alwaysContact); Address *upstreamAddress = (Address *)0; std::vector *upstreamStableEndpoints = (std::vector *)0; - while (i.next(upstreamAddress,upstreamStableEndpoints)) + while (i.next(upstreamAddress,upstreamStableEndpoints)) { RR->sw->requestWhois(tptr,now,*upstreamAddress); + } } // Refresh network config or broadcast network updates to members as needed @@ -357,8 +396,9 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64 // Update online status, post status change as event const bool oldOnline = _online; _online = (((now - lastReceivedFromUpstream) < ZT_PEER_ACTIVITY_TIMEOUT)||(RR->topology->amUpstream())); - if (oldOnline != _online) + if (oldOnline != _online) { postEvent(tptr,_online ? ZT_EVENT_ONLINE : ZT_EVENT_OFFLINE); + } } catch ( ... ) { return ZT_RESULT_FATAL_ERROR_INTERNAL; } @@ -395,8 +435,9 @@ ZT_ResultCode Node::join(uint64_t nwid,void *uptr,void *tptr) { Mutex::Lock _l(_networks_m); SharedPtr &nw = _networks[nwid]; - if (!nw) + if (!nw) { nw = SharedPtr(new Network(RR,tptr,nwid,uptr,(const NetworkConfig *)0)); + } return ZT_RESULT_OK; } @@ -408,17 +449,20 @@ ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr) Mutex::Lock _l(_networks_m); SharedPtr *nw = _networks.get(nwid); RR->sw->removeNetworkQoSControlBlock(nwid); - if (!nw) + if (!nw) { return ZT_RESULT_OK; - if (uptr) + } + if (uptr) { *uptr = (*nw)->userPtr(); + } (*nw)->externalConfig(&ctmp); (*nw)->destroy(); nUserPtr = (*nw)->userPtr(); } - if (nUserPtr) + if (nUserPtr) { RR->node->configureVirtualNetworkPort(tptr,nwid,nUserPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); + } { Mutex::Lock _l(_networks_m); @@ -426,7 +470,8 @@ ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr) } uint64_t tmp[2]; - tmp[0] = nwid; tmp[1] = 0; + tmp[0] = nwid; + tmp[1] = 0; RR->node->stateObjectDelete(tptr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp); return ZT_RESULT_OK; @@ -438,7 +483,9 @@ ZT_ResultCode Node::multicastSubscribe(void *tptr,uint64_t nwid,uint64_t multica if (nw) { nw->multicastSubscribe(tptr,MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff))); return ZT_RESULT_OK; - } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } else { + return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } } ZT_ResultCode Node::multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) @@ -447,7 +494,9 @@ ZT_ResultCode Node::multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,u if (nw) { nw->multicastUnsubscribe(MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff))); return ZT_RESULT_OK; - } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } else { + return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; + } } ZT_ResultCode Node::orbit(void *tptr,uint64_t moonWorldId,uint64_t moonSeed) @@ -481,8 +530,9 @@ ZT_PeerList *Node::peers() const std::sort(peers.begin(),peers.end()); char *buf = (char *)::malloc(sizeof(ZT_PeerList) + (sizeof(ZT_Peer) * peers.size())); - if (!buf) + if (!buf) { return (ZT_PeerList *)0; + } ZT_PeerList *pl = (ZT_PeerList *)buf; pl->peers = (ZT_Peer *)(buf + sizeof(ZT_PeerList)); @@ -501,8 +551,9 @@ ZT_PeerList *Node::peers() const p->versionRev = -1; } p->latency = pi->second->latency(_now); - if (p->latency >= 0xffff) + if (p->latency >= 0xffff) { p->latency = -1; + } p->role = RR->topology->role(pi->second->identity().address()); std::vector< SharedPtr > paths(pi->second->paths(_now)); @@ -562,8 +613,9 @@ ZT_VirtualNetworkList *Node::networks() const Mutex::Lock _l(_networks_m); char *buf = (char *)::malloc(sizeof(ZT_VirtualNetworkList) + (sizeof(ZT_VirtualNetworkConfig) * _networks.size())); - if (!buf) + if (!buf) { return (ZT_VirtualNetworkList *)0; + } ZT_VirtualNetworkList *nl = (ZT_VirtualNetworkList *)buf; nl->networks = (ZT_VirtualNetworkConfig *)(buf + sizeof(ZT_VirtualNetworkList)); @@ -571,16 +623,18 @@ ZT_VirtualNetworkList *Node::networks() const Hashtable< uint64_t,SharedPtr >::Iterator i(*const_cast< Hashtable< uint64_t,SharedPtr > *>(&_networks)); uint64_t *k = (uint64_t *)0; SharedPtr *v = (SharedPtr *)0; - while (i.next(k,v)) + while (i.next(k,v)) { (*v)->externalConfig(&(nl->networks[nl->networkCount++])); + } return nl; } void Node::freeQueryResult(void *qr) { - if (qr) + if (qr) { ::free(qr); + } } int Node::addLocalInterfaceAddress(const struct sockaddr_storage *addr) @@ -619,8 +673,9 @@ int Node::sendUserMessage(void *tptr,uint64_t dest,uint64_t typeId,const void *d void Node::setNetconfMaster(void *networkControllerInstance) { RR->localNetworkController = reinterpret_cast(networkControllerInstance); - if (networkControllerInstance) - RR->localNetworkController->init(RR->identity,this); + if (networkControllerInstance) { + RR->localNetworkController->init(RR->identity, this); + } } /****************************************************************************/ @@ -629,11 +684,13 @@ void Node::setNetconfMaster(void *networkControllerInstance) bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Address &ztaddr,const int64_t localSocket,const InetAddress &remoteAddress) { - if (!Path::isAddressValidForPath(remoteAddress)) + if (!Path::isAddressValidForPath(remoteAddress)) { return false; + } - if (RR->topology->isProhibitedEndpoint(ztaddr,remoteAddress)) + if (RR->topology->isProhibitedEndpoint(ztaddr,remoteAddress)) { return false; + } { Mutex::Lock _l(_networks_m); @@ -643,8 +700,9 @@ bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Address &ztaddr,cons while (i.next(k,v)) { if ((*v)->hasConfig()) { for(unsigned int k=0;k<(*v)->config().staticIpCount;++k) { - if ((*v)->config().staticIps[k].containsAddress(remoteAddress)) + if ((*v)->config().staticIps[k].containsAddress(remoteAddress)) { return false; + } } } } @@ -689,14 +747,18 @@ void Node::ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &de if (destination == RR->identity.address()) { SharedPtr n(network(nwid)); - if (!n) return; + if (!n) { + return; + } n->setConfiguration((void *)0,nc,true); } else { Dictionary *dconf = new Dictionary(); try { if (nc.toDictionary(*dconf,sendLegacyFormatConfig)) { uint64_t configUpdateId = prng(); - if (!configUpdateId) ++configUpdateId; + if (!configUpdateId) { + ++configUpdateId; + } const unsigned int totalSize = dconf->sizeBytes(); unsigned int chunkIndex = 0; @@ -740,7 +802,9 @@ void Node::ncSendRevocation(const Address &destination,const Revocation &rev) { if (destination == RR->identity.address()) { SharedPtr n(network(rev.networkId())); - if (!n) return; + if (!n) { + return; + } n->addCredential((void *)0,RR->identity.address(),rev); } else { Packet outp(destination,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); @@ -758,7 +822,9 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des { if (destination == RR->identity.address()) { SharedPtr n(network(nwid)); - if (!n) return; + if (!n) { + return; + } switch(errorCode) { case NetworkController::NC_ERROR_OBJECT_NOT_FOUND: case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR: @@ -769,11 +835,11 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des break; case NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED: { //fprintf(stderr, "\n\nGot auth required\n\n"); - break; } - default: break; + default: + break; } } else if (requestPacketId) { Packet outp(destination,RR->identity.address(),Packet::VERB_ERROR); @@ -784,12 +850,15 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des //case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR: default: outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND); + Metrics::pkt_error_obj_not_found_out++; break; case NetworkController::NC_ERROR_ACCESS_DENIED: outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_); + Metrics::pkt_error_network_access_denied_out++; break; case NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED: outp.append((unsigned char)Packet::ERROR_NETWORK_AUTHENTICATION_REQUIRED); + Metrics::pkt_error_authentication_required_out++; break; } @@ -1037,9 +1106,15 @@ enum ZT_ResultCode ZT_Node_setPhysicalPathConfiguration(ZT_Node *node,const stru void ZT_version(int *major,int *minor,int *revision) { - if (major) *major = ZEROTIER_ONE_VERSION_MAJOR; - if (minor) *minor = ZEROTIER_ONE_VERSION_MINOR; - if (revision) *revision = ZEROTIER_ONE_VERSION_REVISION; + if (major) { + *major = ZEROTIER_ONE_VERSION_MAJOR; + } + if (minor) { + *minor = ZEROTIER_ONE_VERSION_MINOR; + } + if (revision) { + *revision = ZEROTIER_ONE_VERSION_REVISION; + } } } // extern "C" diff --git a/node/Node.hpp b/node/Node.hpp index 0ddd5cb7b..1f74b8340 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -138,8 +138,9 @@ public: { Mutex::Lock _l(_networks_m); const SharedPtr *n = _networks.get(nwid); - if (n) + if (n) { return *n; + } return SharedPtr(); } @@ -156,8 +157,9 @@ public: Hashtable< uint64_t,SharedPtr >::Iterator i(*const_cast< Hashtable< uint64_t,SharedPtr > * >(&_networks)); uint64_t *k = (uint64_t *)0; SharedPtr *v = (SharedPtr *)0; - while (i.next(k,v)) + while (i.next(k,v)) { nw.push_back(*v); + } return nw; } @@ -223,8 +225,9 @@ public: const uint32_t pid2 = (uint32_t)(packetId >> 32); const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1); for(unsigned long i=0;i<=ZT_EXPECTING_REPLIES_BUCKET_MASK2;++i) { - if (_expectingRepliesTo[bucket][i] == pid2) + if (_expectingRepliesTo[bucket][i] == pid2) { return true; + } } return false; } @@ -258,8 +261,9 @@ public: _localControllerAuthorizations_m.lock(); const int64_t *const at = _localControllerAuthorizations.get(_LocalControllerAuth(nwid,addr)); _localControllerAuthorizations_m.unlock(); - if (at) + if (at) { return ((now - *at) < (ZT_NETWORK_AUTOCONF_DELAY * 3)); + } return false; } diff --git a/node/OutboundMulticast.cpp b/node/OutboundMulticast.cpp index 2d05c49e1..a46a9575e 100644 --- a/node/OutboundMulticast.cpp +++ b/node/OutboundMulticast.cpp @@ -50,20 +50,27 @@ void OutboundMulticast::init( _frameLen = (len < ZT_MAX_MTU) ? len : ZT_MAX_MTU; _etherType = etherType; - if (gatherLimit) flags |= 0x02; + if (gatherLimit) { + flags |= 0x02; + } _packet.setSource(RR->identity.address()); _packet.setVerb(Packet::VERB_MULTICAST_FRAME); _packet.append((uint64_t)nwid); _packet.append(flags); - if (gatherLimit) _packet.append((uint32_t)gatherLimit); - if (src) src.appendTo(_packet); + if (gatherLimit) { + _packet.append((uint32_t)gatherLimit); + } + if (src) { + src.appendTo(_packet); + } dest.mac().appendTo(_packet); _packet.append((uint32_t)dest.adi()); _packet.append((uint16_t)etherType); _packet.append(payload,_frameLen); - if (!disableCompression) + if (!disableCompression) { _packet.compress(); + } memcpy(_frameData,payload,_frameLen); } diff --git a/node/Packet.cpp b/node/Packet.cpp index d9378cc5e..b37cc2640 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -295,7 +295,11 @@ static inline void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) BYTE* d = (BYTE*)dstPtr; const BYTE* s = (const BYTE*)srcPtr; BYTE* const e = (BYTE*)dstEnd; - do { LZ4_copy8(d,s); d+=8; s+=8; } while (d> 3); # else unsigned r; - if (!(val>>32)) { r=4; } else { r=0; val>>=32; } - if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + if (!(val>>32)) { + r=4; + } else { + r=0; + val>>=32; + } + if (!(val>>16)) { + r+=2; + val>>=8; + } else { + val>>=24; + } r += (!val); return r; # endif @@ -369,7 +383,13 @@ static inline unsigned LZ4_NbCommonBytes (reg_t val) return (__builtin_clz((U32)val) >> 3); # else unsigned r; - if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } + if (!(val>>16)) { + r=2; + val>>=8; + } else { + r=0; + val>>=24; + } r += (!val); return r; # endif @@ -384,14 +404,23 @@ static inline unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE while (likely(pIn> ((MINMATCH*8)-(LZ4_HASHLOG+1))); - else + } else { return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); + } } static inline U32 LZ4_hash5(U64 sequence, tableType_t const tableType) @@ -422,25 +452,36 @@ static inline U32 LZ4_hash5(U64 sequence, tableType_t const tableType) static const U64 prime5bytes = 889523592379ULL; static const U64 prime8bytes = 11400714785074694791ULL; const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; - if (LZ4_isLittleEndian()) + if (LZ4_isLittleEndian()) { return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); - else + } else { return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } } FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) { - if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + if ((sizeof(reg_t)==8) && (tableType != byU16)) { + return LZ4_hash5(LZ4_read_ARCH(p), tableType); + } return LZ4_hash4(LZ4_read32(p), tableType); } static inline void LZ4_putPositionOnHash(const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase) { - switch (tableType) - { - case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } - case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } - case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + switch (tableType) { + case byPtr: { + const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; + return; + } + case byU32: { + U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); + return; + } + case byU16: { + U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); + return; + } } } @@ -452,9 +493,18 @@ FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t ta static inline const BYTE* LZ4_getPositionOnHash(U32 h, void* tableBase, tableType_t tableType, const BYTE* srcBase) { - if (tableType == byPtr) { const BYTE** hashTable = (const BYTE**) tableBase; return hashTable[h]; } - if (tableType == byU32) { const U32* const hashTable = (U32*) tableBase; return hashTable[h] + srcBase; } - { const U16* const hashTable = (U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ + if (tableType == byPtr) { + const BYTE** hashTable = (const BYTE**) tableBase; + return hashTable[h]; + } + if (tableType == byU32) { + const U32* const hashTable = (U32*) tableBase; + return hashTable[h] + srcBase; + } + { /* default, to ensure a return */ + const U16* const hashTable = (U16*) tableBase; + return hashTable[h] + srcBase; + } } FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) @@ -493,9 +543,10 @@ FORCE_INLINE int LZ4_compress_generic( U32 forwardH; /* Init conditions */ - if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ - switch(dict) - { + if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) { + return 0; /* Unsupported inputSize, too large (or negative) */ + } + switch(dict) { case noDict: default: base = (const BYTE*)source; @@ -510,12 +561,17 @@ FORCE_INLINE int LZ4_compress_generic( lowLimit = (const BYTE*)source; break; } - if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ - if (inputSize=LZ4_64Klimit)) { + return 0; /* Size too large (not within 64K limit) */ + } + if (inputSizehashTable, tableType, base); - ip++; forwardH = LZ4_hashPosition(ip, tableType); + ip++; + forwardH = LZ4_hashPosition(ip, tableType); /* Main Loop */ for ( ; ; ) { @@ -524,7 +580,8 @@ FORCE_INLINE int LZ4_compress_generic( BYTE* token; /* Find a match */ - { const BYTE* forwardIp = ip; + { + const BYTE* forwardIp = ip; unsigned step = 1; unsigned searchMatchNb = acceleration << LZ4_skipTrigger; do { @@ -533,7 +590,9 @@ FORCE_INLINE int LZ4_compress_generic( forwardIp += step; step = (searchMatchNb++ >> LZ4_skipTrigger); - if (unlikely(forwardIp > mflimit)) goto _last_literals; + if (unlikely(forwardIp > mflimit)) { + goto _last_literals; + } match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); if (dict==usingExtDict) { @@ -543,7 +602,8 @@ FORCE_INLINE int LZ4_compress_generic( } else { refDelta = 0; lowLimit = (const BYTE*)source; - } } + } + } forwardH = LZ4_hashPosition(forwardIp, tableType); LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); @@ -553,21 +613,29 @@ FORCE_INLINE int LZ4_compress_generic( } /* Catch up */ - while (((ip>anchor) & (match+refDelta > lowLimit)) && (unlikely(ip[-1]==match[refDelta-1]))) { ip--; match--; } + while (((ip>anchor) & (match+refDelta > lowLimit)) && (unlikely(ip[-1]==match[refDelta-1]))) { + ip--; + match--; + } /* Encode Literals */ - { unsigned const litLength = (unsigned)(ip - anchor); + { + unsigned const litLength = (unsigned)(ip - anchor); token = op++; if ((outputLimited) && /* Check output buffer overflow */ - (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) { return 0; + } if (litLength >= RUN_MASK) { int len = (int)litLength-RUN_MASK; *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + for(; len >= 255 ; len-=255) { + *op++ = 255; + } *op++ = (BYTE)len; + } else { + *token = (BYTE)(litLength< matchlimit) limit = matchlimit; + if (limit > matchlimit) { + limit = matchlimit; + } matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); ip += MINMATCH + matchCode; if (ip==limit) { @@ -599,8 +671,9 @@ _next_match: } if ( outputLimited && /* Check output buffer overflow */ - (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) + (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) { return 0; + } if (matchCode >= ML_MASK) { *token += ML_MASK; matchCode -= ML_MASK; @@ -612,14 +685,17 @@ _next_match: } op += matchCode / 255; *op++ = (BYTE)(matchCode % 255); - } else + } else { *token += (BYTE)(matchCode); + } } anchor = ip; /* Test end of chunk */ - if (ip > mflimit) break; + if (ip > mflimit) { + break; + } /* Fill table */ LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); @@ -633,12 +709,16 @@ _next_match: } else { refDelta = 0; lowLimit = (const BYTE*)source; - } } + } + } LZ4_putPosition(ip, cctx->hashTable, tableType, base); if ( ((dictIssue==dictSmall) ? (match>=lowRefLimit) : 1) && (match+MAX_DISTANCE>=ip) - && (LZ4_read32(match+refDelta)==LZ4_read32(ip)) ) - { token=op++; *token=0; goto _next_match; } + && (LZ4_read32(match+refDelta)==LZ4_read32(ip)) ) { + token=op++; + *token=0; + goto _next_match; + } /* Prepare next loop */ forwardH = LZ4_hashPosition(++ip, tableType); @@ -646,14 +726,18 @@ _next_match: _last_literals: /* Encode Last Literals */ - { size_t const lastRun = (size_t)(iend - anchor); + { + size_t const lastRun = (size_t)(iend - anchor); if ( (outputLimited) && /* Check output buffer overflow */ - ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) + ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) { return 0; + } if (lastRun >= RUN_MASK) { size_t accumulator = lastRun - RUN_MASK; *op++ = RUN_MASK << ML_BITS; - for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + for(; accumulator >= 255 ; accumulator-=255) { + *op++ = 255; + } *op++ = (BYTE) accumulator; } else { *op++ = (BYTE)(lastRun<= LZ4_compressBound(inputSize)) { - if (inputSize < LZ4_64Klimit) + if (inputSize < LZ4_64Klimit) { return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration); - else + } else { return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + } } else { - if (inputSize < LZ4_64Klimit) + if (inputSize < LZ4_64Klimit) { return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); - else + } else { return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + } } } @@ -741,9 +827,15 @@ FORCE_INLINE int LZ4_decompress_generic( /* Special cases */ - if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => decode everything */ - if ((endOnInput) && (unlikely(outputSize==0))) return ((inputSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ - if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1); + if ((partialDecoding) && (oexit > oend-MFLIMIT)) { + oexit = oend-MFLIMIT; /* targetOutputSize too high => decode everything */ + } + if ((endOnInput) && (unlikely(outputSize==0))) { + return ((inputSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ + } + if ((!endOnInput) && (unlikely(outputSize==0))) { + return (*ip==0?1:-1); + } /* Main Loop : decode sequences */ while (1) { @@ -759,21 +851,32 @@ FORCE_INLINE int LZ4_decompress_generic( s = *ip++; length += s; } while ( likely(endOnInput ? ip(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) ) - || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) - { + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) { if (partialDecoding) { - if (cpy > oend) goto _output_error; /* Error : write attempt beyond end of output buffer */ - if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ + if (cpy > oend) { + goto _output_error; /* Error : write attempt beyond end of output buffer */ + } + if ((endOnInput) && (ip+length > iend)) { + goto _output_error; /* Error : read attempt beyond end of input buffer */ + } } else { - if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ - if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error; /* Error : input must be consumed */ + if ((!endOnInput) && (cpy != oend)) { + goto _output_error; /* Error : block decoding must stop exactly there */ + } + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) { + goto _output_error; /* Error : input must be consumed */ + } } memcpy(op, ip, length); ip += length; @@ -781,12 +884,16 @@ FORCE_INLINE int LZ4_decompress_generic( break; /* Necessarily EOF, due to parsing restrictions */ } LZ4_wildCopy(op, ip, cpy); - ip += length; op = cpy; + ip += length; + op = cpy; /* get offset */ - offset = LZ4_readLE16(ip); ip+=2; + offset = LZ4_readLE16(ip); + ip += 2; match = op - offset; - if ((checkOffset) && (unlikely(match < lowLimit))) goto _output_error; /* Error : offset outside buffers */ + if ((checkOffset) && (unlikely(match < lowLimit))) { + goto _output_error; /* Error : offset outside buffers */ + } LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ /* get matchlength */ @@ -795,16 +902,22 @@ FORCE_INLINE int LZ4_decompress_generic( unsigned s; do { s = *ip++; - if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; + if ((endOnInput) && (ip > iend-LASTLITERALS)) { + goto _output_error; + } length += s; } while (s==255); - if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) { + goto _output_error; /* overflow detection */ + } } length += MINMATCH; /* check external dictionary */ if ((dict==usingExtDict) && (match < lowPrefix)) { - if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error; /* doesn't respect parsing restriction */ + if (unlikely(op+length > oend-LASTLITERALS)) { + goto _output_error; /* doesn't respect parsing restriction */ + } if (length <= (size_t)(lowPrefix-match)) { /* match can be copied as a single segment from external dictionary */ @@ -819,11 +932,14 @@ FORCE_INLINE int LZ4_decompress_generic( if (restSize > (size_t)(op-lowPrefix)) { /* overlap copy */ BYTE* const endOfMatch = op + restSize; const BYTE* copyFrom = lowPrefix; - while (op < endOfMatch) *op++ = *copyFrom++; + while (op < endOfMatch) { + *op++ = *copyFrom++; + } } else { memcpy(op, lowPrefix, restSize); op += restSize; - } } + } + } continue; } @@ -838,31 +954,40 @@ FORCE_INLINE int LZ4_decompress_generic( match += dec32table[offset]; memcpy(op+4, match, 4); match -= dec64; - } else { LZ4_copy8(op, match); match+=8; } + } else { + LZ4_copy8(op, match); + match+=8; + } op += 8; if (unlikely(cpy>oend-12)) { BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1); - if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (cpy > oend-LASTLITERALS) { + goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + } if (op < oCopyLimit) { LZ4_wildCopy(op, match, oCopyLimit); match += oCopyLimit - op; op = oCopyLimit; } - while (op16) LZ4_wildCopy(op+8, match+8, cpy); + if (length>16) { + LZ4_wildCopy(op+8, match+8, cpy); + } } op=cpy; /* correction */ } /* end of decoding */ - if (endOnInput) + if (endOnInput) { return (int) (((char*)op)-dest); /* Nb of output bytes decoded */ - else + } else { return (int) (((const char*)ip)-source); /* Nb of input bytes read */ - + } /* Overflow error detected */ _output_error: return (int) (-(((const char*)ip)-source))-1; @@ -931,8 +1056,9 @@ void Packet::armor(const void *key,bool encryptPayload,const AES aesKeys[2]) uint8_t *const payload = data + ZT_PACKET_IDX_VERB; const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; - if (encryptPayload) + if (encryptPayload) { s20.crypt12(payload,payload,payloadLen); + } uint64_t mac[2]; Poly1305::compute(mac,payload,payloadLen,macKey); @@ -977,14 +1103,17 @@ bool Packet::dearmor(const void *key,const AES aesKeys[2]) uint64_t mac[2]; Poly1305::compute(mac,payload,payloadLen,keyStream); #ifdef ZT_NO_TYPE_PUNNING - if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) { return false; + } #else - if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) // also secure, constant time + if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) { // also secure, constant time return false; + } #endif - if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) { Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),payloadLen); + } } else { Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV); uint64_t macKey[4]; @@ -992,14 +1121,17 @@ bool Packet::dearmor(const void *key,const AES aesKeys[2]) uint64_t mac[2]; Poly1305::compute(mac,payload,payloadLen,macKey); #ifdef ZT_NO_TYPE_PUNNING - if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) { return false; + } #else - if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) // also secure, constant time + if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) { // also secure, constant time return false; + } #endif - if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) { s20.crypt12(payload,payload,payloadLen); + } } return true; } @@ -1011,7 +1143,9 @@ void Packet::cryptField(const void *key,unsigned int start,unsigned int len) { uint8_t *const data = reinterpret_cast(unsafeData()); uint8_t iv[8]; - for(int i=0;i<8;++i) iv[i] = data[i]; + for(int i=0;i<8;++i) { + iv[i] = data[i]; + } iv[7] &= 0xf8; // mask off least significant 3 bits of packet ID / IV since this is unset when this function gets called Salsa20 s20(key,iv); s20.crypt12(data + start,data + start,len); diff --git a/node/Packet.hpp b/node/Packet.hpp index d40e5eb43..e28fec6ab 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -461,8 +461,9 @@ public: */ inline void init(const Packet &p,unsigned int fragStart,unsigned int fragLen,unsigned int fragNo,unsigned int fragTotal) { - if ((fragStart + fragLen) > p.size()) + if ((fragStart + fragLen) > p.size()) { throw ZT_EXCEPTION_OUT_OF_BOUNDS; + } setSize(fragLen + ZT_PROTO_MIN_FRAGMENT_LENGTH); // NOTE: this copies both the IV/packet ID and the destination address. @@ -1217,9 +1218,11 @@ public: */ inline void setFragmented(bool f) { - if (f) + if (f) { (*this)[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_FRAGMENTED; - else (*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_FRAGMENTED); + } else { + (*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_FRAGMENTED); + } } /** @@ -1265,9 +1268,11 @@ public: unsigned char &b = (*this)[ZT_PACKET_IDX_FLAGS]; b = (b & 0xc7) | (unsigned char)((c << 3) & 0x38); // bits: FFCCCHHH // Set DEPRECATED "encrypted" flag -- used by pre-1.0.3 peers - if (c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + if (c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) { b |= ZT_PROTO_FLAG_ENCRYPTED; - else b &= (~ZT_PROTO_FLAG_ENCRYPTED); + } else { + b &= (~ZT_PROTO_FLAG_ENCRYPTED); + } } /** @@ -1405,8 +1410,9 @@ private: // IV and source/destination addresses. Using the addresses divides the // key space into two halves-- A->B and B->A (since order will change). - for(unsigned int i=0;i<18;++i) // 8 + (ZT_ADDRESS_LENGTH * 2) == 18 + for(unsigned int i=0;i<18;++i) { // 8 + (ZT_ADDRESS_LENGTH * 2) == 18 out[i] = in[i] ^ d[i]; + } // Flags, but with hop count masked off. Hop count is altered by forwarding // nodes. It's one of the only parts of a packet modifiable by people @@ -1419,8 +1425,9 @@ private: out[20] = in[20] ^ (unsigned char)((size() >> 8) & 0xff); // little endian // Rest of raw key is used unchanged - for(unsigned int i=21;i<32;++i) + for(unsigned int i=21;i<32;++i) { out[i] = in[i]; + } } }; diff --git a/node/Path.hpp b/node/Path.hpp index eb268695d..31d8e60d1 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -165,8 +165,7 @@ public: unsigned int pl = _latency; if (pl < 0xffff) { _latency = (pl + l) / 2; - } - else { + } else { _latency = l; } } @@ -230,8 +229,9 @@ public: // tunnels due to very spotty performance and low MTU issues over // these IPv6 tunnel links. const uint8_t *ipd = reinterpret_cast(reinterpret_cast(&a)->sin6_addr.s6_addr); - if ((ipd[0] == 0x20)&&(ipd[1] == 0x01)&&(ipd[2] == 0x04)&&(ipd[3] == 0x70)) + if ((ipd[0] == 0x20)&&(ipd[1] == 0x01)&&(ipd[2] == 0x04)&&(ipd[3] == 0x70)) { return false; + } } return true; default: @@ -303,48 +303,48 @@ public: /** * @return Mean latency as reported by the bonding layer */ - inline unsigned int latencyMean() const { return _latencyMean; } + inline float latencyMean() const { return _latencyMean; } /** * @return Latency variance as reported by the bonding layer */ - inline unsigned int latencyVariance() const { return _latencyVariance; } + inline float latencyVariance() const { return _latencyVariance; } /** * @return Packet Loss Ratio as reported by the bonding layer */ - inline unsigned int packetLossRatio() const { return _packetLossRatio; } + inline float packetLossRatio() const { return _packetLossRatio; } /** * @return Packet Error Ratio as reported by the bonding layer */ - inline unsigned int packetErrorRatio() const { return _packetErrorRatio; } + inline float packetErrorRatio() const { return _packetErrorRatio; } /** * @return Whether this path is valid as reported by the bonding layer. The bonding layer * actually checks with Phy to see if the interface is still up */ - inline unsigned int valid() const { return _valid; } + inline bool valid() const { return _valid; } /** * @return Whether this path is eligible for use in a bond as reported by the bonding layer */ - inline unsigned int eligible() const { return _eligible; } + inline bool eligible() const { return _eligible; } /** * @return Whether this path is bonded as reported by the bonding layer */ - inline unsigned int bonded() const { return _bonded; } + inline bool bonded() const { return _bonded; } /** * @return Whether the user-specified MTU for this path (determined by MTU for parent link) */ - inline unsigned int mtu() const { return _mtu; } + inline uint16_t mtu() const { return _mtu; } /** * @return Given link capacity as reported by the bonding layer */ - inline unsigned int givenLinkSpeed() const { return _givenLinkSpeed; } + inline uint32_t givenLinkSpeed() const { return _givenLinkSpeed; } /** * @return Path's quality as reported by the bonding layer diff --git a/node/Peer.cpp b/node/Peer.cpp index 5cb55fa4b..d7f543ead 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -22,37 +22,47 @@ #include "InetAddress.hpp" #include "RingBuffer.hpp" #include "Utils.hpp" +#include "Metrics.hpp" namespace ZeroTier { static unsigned char s_freeRandomByteCounter = 0; -Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) : - RR(renv), - _lastReceive(0), - _lastNontrivialReceive(0), - _lastTriedMemorizedPath(0), - _lastDirectPathPushSent(0), - _lastDirectPathPushReceive(0), - _lastCredentialRequestSent(0), - _lastWhoisRequestReceived(0), - _lastCredentialsReceived(0), - _lastTrustEstablishedPacketReceived(0), - _lastSentFullHello(0), - _lastEchoCheck(0), - _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter), - _vProto(0), - _vMajor(0), - _vMinor(0), - _vRevision(0), - _id(peerIdentity), - _directPathPushCutoffCount(0), - _echoRequestCutoffCount(0), - _localMultipathSupported(false), - _lastComputedAggregateMeanLatency(0) +Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) + : RR(renv) + , _lastReceive(0) + , _lastNontrivialReceive(0) + , _lastTriedMemorizedPath(0) + , _lastDirectPathPushSent(0) + , _lastDirectPathPushReceive(0) + , _lastCredentialRequestSent(0) + , _lastWhoisRequestReceived(0) + , _lastCredentialsReceived(0) + , _lastTrustEstablishedPacketReceived(0) + , _lastSentFullHello(0) + , _lastEchoCheck(0) + , _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter) + , _vProto(0) + , _vMajor(0) + , _vMinor(0) + , _vRevision(0) + , _id(peerIdentity) + , _directPathPushCutoffCount(0) + , _echoRequestCutoffCount(0) + , _localMultipathSupported(false) + , _lastComputedAggregateMeanLatency(0) +#ifndef ZT_NO_PEER_METRICS + , _peer_latency{Metrics::peer_latency.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}}, std::vector{1,3,6,10,30,60,100,300,600,1000})} + , _alive_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","alive"}})} + , _dead_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","dead"}})} + , _incoming_packet{Metrics::peer_packets.Add({{"direction", "rx"},{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})} + , _outgoing_packet{Metrics::peer_packets.Add({{"direction", "tx"},{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})} + , _packet_errors{Metrics::peer_packet_errors.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})} +#endif { - if (!myIdentity.agree(peerIdentity,_key)) + if (!myIdentity.agree(peerIdentity,_key)) { throw ZT_EXCEPTION_INVALID_ARGUMENT; + } uint8_t ktmp[ZT_SYMMETRIC_KEY_SIZE]; KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K0,0,0,ktmp); @@ -89,7 +99,9 @@ void Peer::received( default: break; } - +#ifndef ZT_NO_PEER_METRICS + _incoming_packet++; +#endif recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now); if (trustEstablished) { @@ -145,8 +157,7 @@ void Peer::received( } } } - } - else { + } else { replacePath = i; break; } @@ -199,10 +210,8 @@ void Peer::received( if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH * timerScale : ZT_DIRECT_PATH_PUSH_INTERVAL)) { _lastDirectPathPushSent = now; std::vector pathsToPush(RR->node->directPaths()); - if (! lowBandwidth) { - std::vector ma = RR->sa->whoami(); - pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end()); - } + std::vector ma = RR->sa->whoami(); + pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end()); if (!pathsToPush.empty()) { std::vector::const_iterator p(pathsToPush.begin()); while (p != pathsToPush.end()) { @@ -233,9 +242,11 @@ void Peer::received( ++p; } if (count) { + Metrics::pkt_push_direct_paths_out++; outp->setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count); outp->compress(); outp->armor(_key,true,aesKeysIfSupported()); + Metrics::pkt_push_direct_paths_out++; path->send(RR,tPtr,outp->data(),outp->size(),now); } delete outp; @@ -267,7 +278,9 @@ SharedPtr Peer::getAppropriatePath(int64_t now, bool includeExpired, int32 bestPath = i; } } - } else break; + } else { + break; + } } if (bestPath != ZT_MAX_PEER_NETWORK_PATHS) { return _paths[bestPath].p; @@ -316,7 +329,9 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } break; } - } else break; + } else { + break; + } } Mutex::Lock _l2(other->_paths_m); @@ -339,7 +354,9 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } break; } - } else break; + } else { + break; + } } unsigned int mine = ZT_MAX_PEER_NETWORK_PATHS; @@ -375,6 +392,7 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o outp.append(other->_paths[theirs].p->address().rawIpData(),4); } outp.armor(_key,true,aesKeysIfSupported()); + Metrics::pkt_rendezvous_out++; _paths[mine].p->send(RR,tPtr,outp.data(),outp.size(),now); } else { Packet outp(other->_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS); @@ -389,6 +407,7 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o outp.append(_paths[mine].p->address().rawIpData(),4); } outp.armor(other->_key,true,other->aesKeysIfSupported()); + Metrics::pkt_rendezvous_out++; other->_paths[theirs].p->send(RR,tPtr,outp.data(),outp.size(),now); } ++alt; @@ -429,6 +448,8 @@ void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atA outp.cryptField(_key,startCryptedPortionAt,outp.size() - startCryptedPortionAt); + Metrics::pkt_hello_out++; + if (atAddress) { outp.armor(_key,false,nullptr); // false == don't encrypt full payload, but add MAC RR->node->expectReplyTo(outp.packetId()); @@ -444,6 +465,7 @@ void Peer::attemptToContactAt(void *tPtr,const int64_t localSocket,const InetAdd if ( (!sendFullHello) && (_vProto >= 5) && (!((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0))) ) { Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO); outp.armor(_key,true,aesKeysIfSupported()); + Metrics::pkt_echo_out++; RR->node->expectReplyTo(outp.packetId()); RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size()); } else { @@ -500,56 +522,74 @@ void Peer::performMultipathStateCheck(void *tPtr, int64_t now) unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now) { unsigned int sent = 0; - Mutex::Lock _l(_paths_m); + { + Mutex::Lock _l(_paths_m); - performMultipathStateCheck(tPtr, now); + performMultipathStateCheck(tPtr, now); - const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD); - if (sendFullHello) { - _lastSentFullHello = now; - } - - // Right now we only keep pinging links that have the maximum priority. The - // priority is used to track cluster redirections, meaning that when a cluster - // redirects us its redirect target links override all other links and we - // let those old links expire. - long maxPriority = 0; - for(unsigned int i=0;i= ZT_PEER_PING_PERIOD); + if (sendFullHello) { + _lastSentFullHello = now; } - else { - break; - } - } - bool deletionOccurred = false; - for(unsigned int i=0;ineedsHeartbeat(now))) { - attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,sendFullHello); - _paths[i].p->sent(now); - sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2; + // Right now we only keep pinging links that have the maximum priority. The + // priority is used to track cluster redirections, meaning that when a cluster + // redirects us its redirect target links override all other links and we + // let those old links expire. + long maxPriority = 0; + for(unsigned int i=0;ineedsHeartbeat(now))) { + attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,sendFullHello); + _paths[i].p->sent(now); + sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2; + } + } else { + _paths[i] = _PeerPath(); + deletionOccurred = true; } } - else { - _paths[i] = _PeerPath(); - deletionOccurred = true; + if (!_paths[i].p || deletionOccurred) { + for(unsigned int j=i;jalive(now)) { + alive_path_count_tmp++; + } + else { + dead_path_count_tmp++; } } - deletionOccurred = false; } + _alive_path_count = alive_path_count_tmp; + _dead_path_count = dead_path_count_tmp; +#endif } +#ifndef ZT_NO_PEER_METRICS + _peer_latency.Observe(latency(now)); +#endif return sent; } @@ -571,7 +611,9 @@ void Peer::clusterRedirect(void *tPtr,const SharedPtr &originatingPath,con newPriority = _paths[i].priority; break; } - } else break; + } else { + break; + } } newPriority += 2; @@ -581,8 +623,9 @@ void Peer::clusterRedirect(void *tPtr,const SharedPtr &originatingPath,con for(unsigned int i=0;i= newPriority)&&(!_paths[i].p->address().ipsEqual2(remoteAddress))) { - if (i != j) + if (i != j) { _paths[j] = _paths[i]; + } ++j; } } @@ -612,13 +655,18 @@ void Peer::resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddres _paths[i].p->sent(now); _paths[i].lr = 0; // path will not be used unless it speaks again } - } else break; + } else { + break; + } } } void Peer::recordOutgoingPacket(const SharedPtr &path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now) { +#ifndef ZT_NO_PEER_METRICS + _outgoing_packet++; +#endif if (_localMultipathSupported && _bond) { _bond->recordOutgoingPacket(path, packetId, payloadLength, verb, flowId, now); } @@ -626,6 +674,9 @@ void Peer::recordOutgoingPacket(const SharedPtr &path, const uint64_t pack void Peer::recordIncomingInvalidPacket(const SharedPtr& path) { +#ifndef ZT_NO_PEER_METRICS + _packet_errors++; +#endif if (_localMultipathSupported && _bond) { _bond->recordIncomingInvalidPacket(path); } diff --git a/node/Peer.hpp b/node/Peer.hpp index e6e9b65ed..d03e8f884 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -34,6 +34,7 @@ #include "Mutex.hpp" #include "Bond.hpp" #include "AES.hpp" +#include "Metrics.hpp" #define ZT_PEER_MAX_SERIALIZED_STATE_SIZE (sizeof(Peer) + 32 + (sizeof(Path) * 2)) @@ -50,7 +51,7 @@ class Peer friend class Bond; private: - Peer() {} // disabled to prevent bugs -- should not be constructed uninitialized + Peer() = delete; // disabled to prevent bugs -- should not be constructed uninitialized public: ~Peer() { @@ -119,9 +120,12 @@ public: Mutex::Lock _l(_paths_m); for(unsigned int i=0;iaddress() == addr)) + if (((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION)&&(_paths[i].p->address() == addr)) { return true; - } else break; + } + } else { + break; + } } return false; } @@ -139,8 +143,9 @@ public: inline bool sendDirect(void *tPtr,const void *data,unsigned int len,int64_t now,bool force) { SharedPtr bp(getAppropriatePath(now,force)); - if (bp) + if (bp) { return bp->send(RR,tPtr,data,len,now); + } return false; } @@ -281,7 +286,9 @@ public: std::vector< SharedPtr > pp; Mutex::Lock _l(_paths_m); for(unsigned int i=0;i bp(getAppropriatePath(now,false)); if (bp) { - return bp->latency(); + return (unsigned int)bp->latency(); } return 0xffff; } @@ -334,11 +341,13 @@ public: inline unsigned int relayQuality(const int64_t now) { const uint64_t tsr = now - _lastReceive; - if (tsr >= ZT_PEER_ACTIVITY_TIMEOUT) + if (tsr >= ZT_PEER_ACTIVITY_TIMEOUT) { return (~(unsigned int)0); + } unsigned int l = latency(now); - if (!l) + if (!l) { l = 0xffff; + } return (l * (((unsigned int)tsr / (ZT_PEER_PING_PERIOD + 1000)) + 1)); } @@ -380,9 +389,11 @@ public: */ inline bool rateGatePushDirectPaths(const int64_t now) { - if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) + if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) { ++_directPathPushCutoffCount; - else _directPathPushCutoffCount = 0; + } else { + _directPathPushCutoffCount = 0; + } _lastDirectPathPushReceive = now; return (_directPathPushCutoffCount < ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT); } @@ -444,13 +455,16 @@ public: Mutex::Lock _l(_paths_m); unsigned int pc = 0; for(unsigned int i=0;iaddress().serialize(b); + } } } @@ -459,31 +473,39 @@ public: { try { unsigned int ptr = 0; - if (b[ptr++] != 2) + if (b[ptr++] != 2) { return SharedPtr(); + } Identity id; ptr += id.deserialize(b,ptr); - if (!id) + if (!id) { return SharedPtr(); + } SharedPtr p(new Peer(renv,renv->identity,id)); - p->_vProto = b.template at(ptr); ptr += 2; - p->_vMajor = b.template at(ptr); ptr += 2; - p->_vMinor = b.template at(ptr); ptr += 2; - p->_vRevision = b.template at(ptr); ptr += 2; + p->_vProto = b.template at(ptr); + ptr += 2; + p->_vMajor = b.template at(ptr); + ptr += 2; + p->_vMinor = b.template at(ptr); + ptr += 2; + p->_vRevision = b.template at(ptr); + ptr += 2; // When we deserialize from the cache we don't actually restore paths. We // just try them and then re-learn them if they happen to still be up. // Paths are fairly ephemeral in the real world in most cases. - const unsigned int tryPathCount = b.template at(ptr); ptr += 2; + const unsigned int tryPathCount = b.template at(ptr); + ptr += 2; for(unsigned int i=0;iattemptToContactAt(tPtr,-1,inaddr,now,true); + } } catch ( ... ) { break; } @@ -576,6 +598,15 @@ private: int32_t _lastComputedAggregateMeanLatency; SharedPtr _bond; + +#ifndef ZT_NO_PEER_METRICS + prometheus::Histogram &_peer_latency; + prometheus::simpleapi::gauge_metric_t _alive_path_count; + prometheus::simpleapi::gauge_metric_t _dead_path_count; + prometheus::simpleapi::counter_metric_t _incoming_packet; + prometheus::simpleapi::counter_metric_t _outgoing_packet; + prometheus::simpleapi::counter_metric_t _packet_errors; +#endif }; } // namespace ZeroTier diff --git a/node/Poly1305.cpp b/node/Poly1305.cpp index c670e5d05..382465135 100644 --- a/node/Poly1305.cpp +++ b/node/Poly1305.cpp @@ -192,8 +192,9 @@ static inline void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) if (st->leftover) { size_t i = st->leftover; st->buffer[i] = 1; - for (i = i + 1; i < poly1305_block_size; i++) + for (i = i + 1; i < poly1305_block_size; i++) { st->buffer[i] = 0; + } st->final = 1; poly1305_blocks(st, st->buffer, poly1305_block_size); } @@ -391,8 +392,9 @@ poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) { if (st->leftover) { size_t i = st->leftover; st->buffer[i++] = 1; - for (; i < poly1305_block_size; i++) + for (; i < poly1305_block_size; i++) { st->buffer[i] = 0; + } st->final = 1; poly1305_blocks(st, st->buffer, poly1305_block_size); } @@ -477,15 +479,18 @@ static inline void poly1305_update(poly1305_context *ctx, const unsigned char *m /* handle leftover */ if (st->leftover) { size_t want = (poly1305_block_size - st->leftover); - if (want > bytes) + if (want > bytes) { want = bytes; - for (i = 0; i < want; i++) + } + for (i = 0; i < want; i++) { st->buffer[st->leftover + i] = m[i]; + } bytes -= want; m += want; st->leftover += want; - if (st->leftover < poly1305_block_size) + if (st->leftover < poly1305_block_size) { return; + } poly1305_blocks(st, st->buffer, poly1305_block_size); st->leftover = 0; } @@ -500,8 +505,9 @@ static inline void poly1305_update(poly1305_context *ctx, const unsigned char *m /* store leftover */ if (bytes) { - for (i = 0; i < bytes; i++) + for (i = 0; i < bytes; i++) { st->buffer[st->leftover + i] = m[i]; + } st->leftover += bytes; } } diff --git a/node/Revocation.cpp b/node/Revocation.cpp index f196b43c7..9af6d5681 100644 --- a/node/Revocation.cpp +++ b/node/Revocation.cpp @@ -23,8 +23,9 @@ namespace ZeroTier { int Revocation::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) { return -1; + } const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); if (!id) { RR->sw->requestWhois(tPtr,RR->node->now(),_signedBy); diff --git a/node/Revocation.hpp b/node/Revocation.hpp index e1c4e18fc..dd717168d 100644 --- a/node/Revocation.hpp +++ b/node/Revocation.hpp @@ -118,7 +118,9 @@ public: template inline void serialize(Buffer &b,const bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } b.append((uint32_t)0); // 4 unused bytes, currently set to 0 b.append(_id); @@ -140,7 +142,9 @@ public: // This is the size of any additional fields, currently 0. b.append((uint16_t)0); - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } } template @@ -151,14 +155,21 @@ public: unsigned int p = startAt; p += 4; // 4 bytes, currently unused - _id = b.template at(p); p += 4; - _networkId = b.template at(p); p += 8; + _id = b.template at(p); + p += 4; + _networkId = b.template at(p); + p += 8; p += 4; // 4 bytes, currently unused - _credentialId = b.template at(p); p += 4; - _threshold = (int64_t)b.template at(p); p += 8; - _flags = b.template at(p); p += 8; - _target.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _credentialId = b.template at(p); + p += 4; + _threshold = (int64_t)b.template at(p); + p += 8; + _flags = b.template at(p); + p += 8; + _target.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; _type = (Credential::Type)b[p++]; if (b[p++] == 1) { @@ -166,14 +177,17 @@ public: p += 2; memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; - } else throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } else { + throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } } else { p += 2 + b.template at(p); } p += 2 + b.template at(p); - if (p > b.size()) + if (p > b.size()) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } return (p - startAt); } diff --git a/node/RingBuffer.hpp b/node/RingBuffer.hpp index e2b90ce5c..53834b4f7 100644 --- a/node/RingBuffer.hpp +++ b/node/RingBuffer.hpp @@ -193,11 +193,9 @@ public: { if (end == begin) { return wrap ? S : 0; - } - else if (end > begin) { + } else if (end > begin) { return end - begin; - } - else { + } else { return S + end - begin; } } diff --git a/node/SHA512.cpp b/node/SHA512.cpp index 2fe126a84..bd81647d0 100644 --- a/node/SHA512.cpp +++ b/node/SHA512.cpp @@ -58,12 +58,15 @@ static ZT_INLINE void sha512_compress(sha512_state *const md,uint8_t *const buf) uint64_t S[8], W[80], t0, t1; int i; - for (i = 0; i < 8; i++) + for (i = 0; i < 8; i++) { S[i] = md->state[i]; - for (i = 0; i < 16; i++) + } + for (i = 0; i < 16; i++) { LOAD64H(W[i], buf + (8*i)); - for (i = 16; i < 80; i++) + } + for (i = 16; i < 80; i++) { W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + } #define RND(a,b,c,d,e,f,g,h,i) \ t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ @@ -82,8 +85,9 @@ static ZT_INLINE void sha512_compress(sha512_state *const md,uint8_t *const buf) RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); } - for (i = 0; i < 8; i++) + for (i = 0; i < 8; i++) { md->state[i] = md->state[i] + S[i]; + } } static ZT_INLINE void sha384_init(sha512_state *const md) diff --git a/node/Salsa20.cpp b/node/Salsa20.cpp index ca9e47185..63573546e 100644 --- a/node/Salsa20.cpp +++ b/node/Salsa20.cpp @@ -121,8 +121,9 @@ void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; #endif - if (!bytes) + if (!bytes) { return; + } #ifndef ZT_SALSA20_SSE j0 = _state.i[0]; @@ -145,8 +146,9 @@ void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) for (;;) { if (bytes < 64) { - for (i = 0;i < bytes;++i) + for (i = 0;i < bytes;++i) { tmp[i] = m[i]; + } m = tmp; ctarget = c; c = tmp; @@ -589,8 +591,9 @@ void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) if (bytes <= 64) { if (bytes < 64) { - for (i = 0;i < bytes;++i) + for (i = 0;i < bytes;++i) { ctarget[i] = c[i]; + } } #ifndef ZT_SALSA20_SSE @@ -620,8 +623,9 @@ void Salsa20::crypt20(const void *in,void *out,unsigned int bytes) uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; #endif - if (!bytes) + if (!bytes) { return; + } #ifndef ZT_SALSA20_SSE j0 = _state.i[0]; @@ -644,8 +648,9 @@ void Salsa20::crypt20(const void *in,void *out,unsigned int bytes) for (;;) { if (bytes < 64) { - for (i = 0;i < bytes;++i) + for (i = 0;i < bytes;++i) { tmp[i] = m[i]; + } m = tmp; ctarget = c; c = tmp; @@ -1320,8 +1325,9 @@ void Salsa20::crypt20(const void *in,void *out,unsigned int bytes) if (bytes <= 64) { if (bytes < 64) { - for (i = 0;i < bytes;++i) + for (i = 0;i < bytes;++i) { ctarget[i] = c[i]; + } } #ifndef ZT_SALSA20_SSE diff --git a/node/SelfAwareness.cpp b/node/SelfAwareness.cpp index 45bc25410..c87f4e3e8 100644 --- a/node/SelfAwareness.cpp +++ b/node/SelfAwareness.cpp @@ -61,8 +61,9 @@ void SelfAwareness::iam(void *tPtr,const Address &reporter,const int64_t receive { const InetAddress::IpScope scope = myPhysicalAddress.ipScope(); - if ((scope != reporterPhysicalAddress.ipScope())||(scope == InetAddress::IP_SCOPE_NONE)||(scope == InetAddress::IP_SCOPE_LOOPBACK)||(scope == InetAddress::IP_SCOPE_MULTICAST)) + if ((scope != reporterPhysicalAddress.ipScope())||(scope == InetAddress::IP_SCOPE_NONE)||(scope == InetAddress::IP_SCOPE_LOOPBACK)||(scope == InetAddress::IP_SCOPE_MULTICAST)) { return; + } Mutex::Lock _l(_phy_m); PhySurfaceEntry &entry = _phy[PhySurfaceKey(reporter,receivedOnLocalSocket,reporterPhysicalAddress,scope)]; @@ -83,8 +84,9 @@ void SelfAwareness::iam(void *tPtr,const Address &reporter,const int64_t receive PhySurfaceKey *k = (PhySurfaceKey *)0; PhySurfaceEntry *e = (PhySurfaceEntry *)0; while (i.next(k,e)) { - if ((k->reporterPhysicalAddress != reporterPhysicalAddress)&&(k->scope == scope)) + if ((k->reporterPhysicalAddress != reporterPhysicalAddress)&&(k->scope == scope)) { _phy.erase(*k); + } } } @@ -121,8 +123,9 @@ void SelfAwareness::clean(int64_t now) PhySurfaceKey *k = (PhySurfaceKey *)0; PhySurfaceEntry *e = (PhySurfaceEntry *)0; while (i.next(k,e)) { - if ((now - e->ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT) + if ((now - e->ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT) { _phy.erase(*k); + } } } diff --git a/node/SharedPtr.hpp b/node/SharedPtr.hpp index dff0fa4e9..1116be40a 100644 --- a/node/SharedPtr.hpp +++ b/node/SharedPtr.hpp @@ -37,8 +37,9 @@ public: ~SharedPtr() { if (_ptr) { - if (--_ptr->__refCount <= 0) + if (--_ptr->__refCount <= 0) { delete _ptr; + } } } @@ -47,8 +48,9 @@ public: if (_ptr != sp._ptr) { T *p = sp._getAndInc(); if (_ptr) { - if (--_ptr->__refCount <= 0) + if (--_ptr->__refCount <= 0) { delete _ptr; + } } _ptr = p; } @@ -97,8 +99,9 @@ public: inline void zero() { if (_ptr) { - if (--_ptr->__refCount <= 0) + if (--_ptr->__refCount <= 0) { delete _ptr; + } _ptr = (T *)0; } } @@ -108,8 +111,9 @@ public: */ inline int references() { - if (_ptr) + if (_ptr) { return _ptr->__refCount.load(); + } return 0; } @@ -123,8 +127,9 @@ public: private: inline T *_getAndInc() const { - if (_ptr) + if (_ptr) { ++_ptr->__refCount; + } return _ptr; } T *_ptr; diff --git a/node/Switch.cpp b/node/Switch.cpp index 59ed34a04..5cbbeff85 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -31,6 +31,7 @@ #include "SelfAwareness.hpp" #include "Packet.hpp" #include "Trace.hpp" +#include "Metrics.hpp" namespace ZeroTier { @@ -45,8 +46,9 @@ Switch::Switch(const RuntimeEnvironment *renv) : // Returns true if packet appears valid; pos and proto will be set static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) { - if (frameLen < 40) + if (frameLen < 40) { return false; + } pos = 40; proto = frameData[6]; while (pos <= frameLen) { @@ -55,8 +57,9 @@ static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsig case 43: // routing case 60: // destination options case 135: // mobility options - if ((pos + 8) > frameLen) + if ((pos + 8) > frameLen) { return false; // invalid! + } proto = frameData[pos]; pos += ((unsigned int)frameData[pos + 1] * 8) + 8; break; @@ -87,16 +90,19 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre * locate peers with versions <1.0.4. */ const Address beaconAddr(reinterpret_cast(data) + 8,5); - if (beaconAddr == RR->identity.address()) + if (beaconAddr == RR->identity.address()) { return; - if (!RR->node->shouldUsePathForZeroTierTraffic(tPtr,beaconAddr,localSocket,fromAddr)) + } + if (!RR->node->shouldUsePathForZeroTierTraffic(tPtr,beaconAddr,localSocket,fromAddr)) { return; + } const SharedPtr peer(RR->topology->getPeer(tPtr,beaconAddr)); if (peer) { // we'll only respond to beacons from known peers if ((now - _lastBeaconResponse) >= 2500) { // limit rate of responses _lastBeaconResponse = now; Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + Metrics::pkt_nop_out++; path->send(RR,tPtr,outp.data(),outp.size(),now); } } @@ -109,8 +115,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre const Address destination(fragment.destination()); if (destination != RR->identity.address()) { - if ( (!RR->topology->amUpstream()) && (!path->trustEstablished(now)) ) + if ( (!RR->topology->amUpstream()) && (!path->trustEstablished(now)) ) { return; + } if (fragment.hops() < ZT_RELAY_MAX_HOPS) { fragment.incrementHops(); @@ -121,8 +128,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((!relayTo)||(!relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,false))) { // Don't know peer or no direct path -- so relay via someone upstream relayTo = RR->topology->getUpstreamPeer(); - if (relayTo) + if (relayTo) { relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,true); + } } } } else { @@ -158,8 +166,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if (Utils::countBits(rq->haveFragments |= (1 << fragmentNumber)) == totalFragments) { // We have all fragments -- assemble and process full Packet - for(unsigned int f=1;ffrag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); + } if (rq->frag0.tryDecode(RR,tPtr,flowId)) { rq->timestamp = 0; // packet decoded, free entry @@ -178,12 +187,14 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre const Address destination(reinterpret_cast(data) + 8,ZT_ADDRESS_LENGTH); const Address source(reinterpret_cast(data) + 13,ZT_ADDRESS_LENGTH); - if (source == RR->identity.address()) + if (source == RR->identity.address()) { return; + } if (destination != RR->identity.address()) { - if ( (!RR->topology->amUpstream()) && (!path->trustEstablished(now)) && (source != RR->identity.address()) ) + if ( (!RR->topology->amUpstream()) && (!path->trustEstablished(now)) && (source != RR->identity.address()) ) { return; + } Packet packet(data,len); @@ -193,16 +204,18 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((relayTo)&&(relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,false))) { if ((source != RR->identity.address())&&(_shouldUnite(now,source,destination))) { const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); - if (sourcePeer) + if (sourcePeer) { relayTo->introduce(tPtr,now,sourcePeer); + } } } else { relayTo = RR->topology->getUpstreamPeer(); if ((relayTo)&&(relayTo->address() != source)) { if (relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,true)) { const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); - if (sourcePeer) + if (sourcePeer) { relayTo->introduce(tPtr,now,sourcePeer); + } } } } @@ -240,8 +253,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre // We have all fragments -- assemble and process full Packet rq->frag0.init(data,len,path,now); - for(unsigned int f=1;ftotalFragments;++f) + for(unsigned int f=1;ftotalFragments;++f) { rq->frag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); + } if (rq->frag0.tryDecode(RR,tPtr,flowId)) { rq->timestamp = 0; // packet decoded, free entry @@ -277,8 +291,9 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) { - if (!network->hasConfig()) + if (!network->hasConfig()) { return; + } // Check if this packet is from someone other than the tap -- i.e. bridged in bool fromBridged; @@ -401,8 +416,9 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const if ((sipNetmaskBits == 88)&&(my6[0] == 0xfd)&&(my6[9] == 0x99)&&(my6[10] == 0x93)) { // ZT-RFC4193 /88 ??? unsigned int ptr = 0; while (ptr != 11) { - if (pkt6[ptr] != my6[ptr]) + if (pkt6[ptr] != my6[ptr]) { break; + } ++ptr; } if (ptr == 11) { // prefix match! @@ -414,8 +430,9 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const if ( (my6[0] == 0xfc) && (my6[1] == (uint8_t)((nwid32 >> 24) & 0xff)) && (my6[2] == (uint8_t)((nwid32 >> 16) & 0xff)) && (my6[3] == (uint8_t)((nwid32 >> 8) & 0xff)) && (my6[4] == (uint8_t)(nwid32 & 0xff))) { unsigned int ptr = 0; while (ptr != 5) { - if (pkt6[ptr] != my6[ptr]) + if (pkt6[ptr] != my6[ptr]) { break; + } ++ptr; } if (ptr == 5) { // prefix match! @@ -431,27 +448,63 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const const MAC peerMac(v6EmbeddedAddress,network->id()); uint8_t adv[72]; - adv[0] = 0x60; adv[1] = 0x00; adv[2] = 0x00; adv[3] = 0x00; - adv[4] = 0x00; adv[5] = 0x20; - adv[6] = 0x3a; adv[7] = 0xff; - for(int i=0;i<16;++i) adv[8 + i] = pkt6[i]; - for(int i=0;i<16;++i) adv[24 + i] = my6[i]; - adv[40] = 0x88; adv[41] = 0x00; - adv[42] = 0x00; adv[43] = 0x00; // future home of checksum - adv[44] = 0x60; adv[45] = 0x00; adv[46] = 0x00; adv[47] = 0x00; - for(int i=0;i<16;++i) adv[48 + i] = pkt6[i]; - adv[64] = 0x02; adv[65] = 0x01; - adv[66] = peerMac[0]; adv[67] = peerMac[1]; adv[68] = peerMac[2]; adv[69] = peerMac[3]; adv[70] = peerMac[4]; adv[71] = peerMac[5]; + adv[0] = 0x60; + adv[1] = 0x00; + adv[2] = 0x00; + adv[3] = 0x00; + adv[4] = 0x00; + adv[5] = 0x20; + adv[6] = 0x3a; + adv[7] = 0xff; + for(int i=0;i<16;++i) { + adv[8 + i] = pkt6[i]; + } + for(int i=0;i<16;++i) { + adv[24 + i] = my6[i]; + } + adv[40] = 0x88; + adv[41] = 0x00; + adv[42] = 0x00; + adv[43] = 0x00; // future home of checksum + adv[44] = 0x60; + adv[45] = 0x00; + adv[46] = 0x00; + adv[47] = 0x00; + for(int i=0;i<16;++i) { + adv[48 + i] = pkt6[i]; + } + adv[64] = 0x02; + adv[65] = 0x01; + adv[66] = peerMac[0]; + adv[67] = peerMac[1]; + adv[68] = peerMac[2]; + adv[69] = peerMac[3]; + adv[70] = peerMac[4]; + adv[71] = peerMac[5]; uint16_t pseudo_[36]; uint8_t *const pseudo = reinterpret_cast(pseudo_); - for(int i=0;i<32;++i) pseudo[i] = adv[8 + i]; - pseudo[32] = 0x00; pseudo[33] = 0x00; pseudo[34] = 0x00; pseudo[35] = 0x20; - pseudo[36] = 0x00; pseudo[37] = 0x00; pseudo[38] = 0x00; pseudo[39] = 0x3a; - for(int i=0;i<32;++i) pseudo[40 + i] = adv[40 + i]; + for(int i=0;i<32;++i) { + pseudo[i] = adv[8 + i]; + } + pseudo[32] = 0x00; + pseudo[33] = 0x00; + pseudo[34] = 0x00; + pseudo[35] = 0x20; + pseudo[36] = 0x00; + pseudo[37] = 0x00; + pseudo[38] = 0x00; + pseudo[39] = 0x3a; + for(int i=0;i<32;++i) { + pseudo[40 + i] = adv[40 + i]; + } uint32_t checksum = 0; - for(int i=0;i<36;++i) checksum += Utils::hton(pseudo_[i]); - while ((checksum >> 16)) checksum = (checksum & 0xffff) + (checksum >> 16); + for(int i=0;i<36;++i) { + checksum += Utils::hton(pseudo_[i]); + } + while ((checksum >> 16)) { + checksum = (checksum & 0xffff) + (checksum >> 16); + } checksum = ~checksum; adv[42] = (checksum >> 8) & 0xff; adv[43] = checksum & 0xff; @@ -472,8 +525,9 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const * Note that some OSes, most notably Linux, do this for you by learning * multicast addresses on bridge interfaces and subscribing each slave. * But in that case this does no harm, as the sets are just merged. */ - if (fromBridged) + if (fromBridged) { network->learnBridgedMulticastGroup(tPtr,multicastGroup,RR->node->now()); + } // First pass sets noTee to false, but noTee is set to true in OutboundMulticast to prevent duplicates. if (!network->filterOutgoingPacket(tPtr,false,RR->identity.address(),Address(),from,to,(const uint8_t *)data,len,etherType,vlanId,qosBucket)) { @@ -562,12 +616,15 @@ void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const } else { // Otherwise pick a random set of them while (numBridges < ZT_MAX_BRIDGE_SPAM) { - if (ab == activeBridges.end()) + if (ab == activeBridges.end()) { ab = activeBridges.begin(); + } if (((unsigned long)RR->node->prng() % (unsigned long)activeBridges.size()) == 0) { bridges[numBridges++] = *ab; ++ab; - } else ++ab; + } else { + ++ab; + } } } } @@ -626,11 +683,13 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & if (nqcb->oldQueues[i]->id == qosBucket) { selectedQueue = nqcb->oldQueues[i]; } - } if (i < nqcb->newQueues.size()) { // search new queues (this would imply not often-used queues) + } + if (i < nqcb->newQueues.size()) { // search new queues (this would imply not often-used queues) if (nqcb->newQueues[i]->id == qosBucket) { selectedQueue = nqcb->newQueues[i]; } - } if (i < nqcb->inactiveQueues.size()) { // search inactive queues + } + if (i < nqcb->inactiveQueues.size()) { // search inactive queues if (nqcb->inactiveQueues[i]->id == qosBucket) { selectedQueue = nqcb->inactiveQueues[i]; // move queue to end of NEW queue list @@ -654,8 +713,7 @@ void Switch::aqm_enqueue(void *tPtr, const SharedPtr &network, Packet & // Drop a packet if necessary ManagedQueue *selectedQueueToDropFrom = nullptr; - if (nqcb->_currEnqueuedPackets > ZT_AQM_MAX_ENQUEUED_PACKETS) - { + if (nqcb->_currEnqueuedPackets > ZT_AQM_MAX_ENQUEUED_PACKETS) { // DEBUG_INFO("too many enqueued packets (%d), finding packet to drop", nqcb->_currEnqueuedPackets); int maxQueueLength = 0; for (size_t i=0; i &network, Packet & maxQueueLength = nqcb->oldQueues[i]->byteLength; selectedQueueToDropFrom = nqcb->oldQueues[i]; } - } if (i < nqcb->newQueues.size()) { + } + if (i < nqcb->newQueues.size()) { if (nqcb->newQueues[i]->byteLength > maxQueueLength) { maxQueueLength = nqcb->newQueues[i]->byteLength; selectedQueueToDropFrom = nqcb->newQueues[i]; } - } if (i < nqcb->inactiveQueues.size()) { + } + if (i < nqcb->inactiveQueues.size()) { if (nqcb->inactiveQueues[i]->byteLength > maxQueueLength) { maxQueueLength = nqcb->inactiveQueues[i]->byteLength; selectedQueueToDropFrom = nqcb->inactiveQueues[i]; @@ -784,8 +844,7 @@ void Switch::aqm_dequeue(void *tPtr) // DEBUG_INFO("moving q=%p from NEW to OLD list", queueAtFrontOfList); oldQueues->push_back(queueAtFrontOfList); currQueues->erase(currQueues->begin()); - } - else { + } else { int len = entryToEmit->packet.payloadLength(); queueAtFrontOfList->byteLength -= len; queueAtFrontOfList->byteCredit -= len; @@ -817,8 +876,7 @@ void Switch::aqm_dequeue(void *tPtr) // Move to inactive list of queues inactiveQueues->push_back(queueAtFrontOfList); currQueues->erase(currQueues->begin()); - } - else { + } else { int len = entryToEmit->packet.payloadLength(); queueAtFrontOfList->byteLength -= len; queueAtFrontOfList->byteCredit -= len; @@ -850,8 +908,10 @@ void Switch::removeNetworkQoSControlBlock(uint64_t nwid) void Switch::send(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) { const Address dest(packet.destination()); - if (dest == RR->identity.address()) + if (dest == RR->identity.address()) { return; + } + _recordOutgoingPacketMetrics(packet); if (!_trySend(tPtr,packet,encrypt,flowId)) { { Mutex::Lock _l(_txQueue_m); @@ -860,22 +920,26 @@ void Switch::send(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) } _txQueue.push_back(TXQueueEntry(dest,RR->node->now(),packet,encrypt,flowId)); } - if (!RR->topology->getPeer(tPtr,dest)) + if (!RR->topology->getPeer(tPtr,dest)) { requestWhois(tPtr,RR->node->now(),dest); + } } } void Switch::requestWhois(void *tPtr,const int64_t now,const Address &addr) { - if (addr == RR->identity.address()) + if (addr == RR->identity.address()) { return; + } { Mutex::Lock _l(_lastSentWhoisRequest_m); int64_t &last = _lastSentWhoisRequest[addr]; - if ((now - last) < ZT_WHOIS_RETRY_DELAY) + if ((now - last) < ZT_WHOIS_RETRY_DELAY) { return; - else last = now; + } else { + last = now; + } } const SharedPtr upstream(RR->topology->getUpstreamPeer()); @@ -899,8 +963,9 @@ void Switch::doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer) RXQueueEntry *const rq = &(_rxQueue[ptr]); Mutex::Lock rql(rq->lock); if ((rq->timestamp)&&(rq->complete)) { - if ((rq->frag0.tryDecode(RR,tPtr,rq->flowId))||((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) + if ((rq->frag0.tryDecode(RR,tPtr,rq->flowId))||((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) { rq->timestamp = 0; + } } } @@ -923,8 +988,9 @@ void Switch::doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer) unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) { const uint64_t timeSinceLastCheck = now - _lastCheckedQueues; - if (timeSinceLastCheck < ZT_WHOIS_RETRY_DELAY) + if (timeSinceLastCheck < ZT_WHOIS_RETRY_DELAY) { return (unsigned long)(ZT_WHOIS_RETRY_DELAY - timeSinceLastCheck); + } _lastCheckedQueues = now; std::vector
needWhois; @@ -937,14 +1003,16 @@ unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) } else if ((now - txi->creationTime) > ZT_TRANSMIT_QUEUE_TIMEOUT) { _txQueue.erase(txi++); } else { - if (!RR->topology->getPeer(tPtr,txi->dest)) + if (!RR->topology->getPeer(tPtr,txi->dest)) { needWhois.push_back(txi->dest); + } ++txi; } } } - for(std::vector
::const_iterator i(needWhois.begin());i!=needWhois.end();++i) + for(std::vector
::const_iterator i(needWhois.begin());i!=needWhois.end();++i) { requestWhois(tPtr,now,*i); + } for(unsigned int ptr=0;ptrtimestamp = 0; } else { const Address src(rq->frag0.source()); - if (!RR->topology->getPeer(tPtr,src)) + if (!RR->topology->getPeer(tPtr,src)) { requestWhois(tPtr,now,src); + } } } } @@ -966,8 +1035,9 @@ unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) _LastUniteKey *k = (_LastUniteKey *)0; uint64_t *v = (uint64_t *)0; while (i.next(k,v)) { - if ((now - *v) >= (ZT_MIN_UNITE_INTERVAL * 8)) + if ((now - *v) >= (ZT_MIN_UNITE_INTERVAL * 8)) { _lastUniteAttempt.erase(*k); + } } } @@ -977,8 +1047,9 @@ unsigned long Switch::doTimerTasks(void *tPtr,int64_t now) Address *a = (Address *)0; int64_t *ts = (int64_t *)0; while (i.next(a,ts)) { - if ((now - *ts) > (ZT_WHOIS_RETRY_DELAY * 2)) + if ((now - *ts) > (ZT_WHOIS_RETRY_DELAY * 2)) { _lastSentWhoisRequest.erase(*a); + } } } @@ -1015,15 +1086,15 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) } } return true; - } - else { + } else { viaPath = peer->getAppropriatePath(now,false,flowId); if (!viaPath) { peer->tryMemorizedPath(tPtr,now); // periodically attempt memorized or statically defined paths, if any are known const SharedPtr relay(RR->topology->getUpstreamPeer()); if ( (!relay) || (!(viaPath = relay->getAppropriatePath(now,false,flowId))) ) { - if (!(viaPath = peer->getAppropriatePath(now,true,flowId))) + if (!(viaPath = peer->getAppropriatePath(now,true,flowId))) { return false; + } } } if (viaPath) { @@ -1065,8 +1136,9 @@ void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr peer,SharedPtr peer,SharedPtr viaPath,uint16_t userSpecifiedMtu, int64_t now,Packet &packet,bool encrypt,int32_t flowId); + void _recordOutgoingPacketMetrics(const Packet &p); const RuntimeEnvironment *const RR; int64_t _lastBeaconResponse; @@ -240,8 +241,9 @@ private: const unsigned int current = static_cast(_rxQueuePtr.load()); for(unsigned int k=1;k<=ZT_RX_QUEUE_SIZE;++k) { RXQueueEntry *rq = &(_rxQueue[(current - k) % ZT_RX_QUEUE_SIZE]); - if ((rq->packetId == packetId)&&(rq->timestamp)) + if ((rq->packetId == packetId)&&(rq->timestamp)) { return rq; + } } ++_rxQueuePtr; return &(_rxQueue[static_cast(current) % ZT_RX_QUEUE_SIZE]); diff --git a/node/Tag.cpp b/node/Tag.cpp index 78f5d1f3c..537da11c9 100644 --- a/node/Tag.cpp +++ b/node/Tag.cpp @@ -23,8 +23,9 @@ namespace ZeroTier { int Tag::verify(const RuntimeEnvironment *RR,void *tPtr) const { - if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) { return -1; + } const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); if (!id) { RR->sw->requestWhois(tPtr,RR->node->now(),_signedBy); diff --git a/node/Tag.hpp b/node/Tag.hpp index cad8a5c31..c8d00b0c6 100644 --- a/node/Tag.hpp +++ b/node/Tag.hpp @@ -116,7 +116,9 @@ public: template inline void serialize(Buffer &b,const bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } b.append(_networkId); b.append(_ts); @@ -133,7 +135,9 @@ public: b.append((uint16_t)0); // length of additional fields, currently 0 - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } } template @@ -143,26 +147,35 @@ public: *this = Tag(); - _networkId = b.template at(p); p += 8; - _ts = b.template at(p); p += 8; - _id = b.template at(p); p += 4; + _networkId = b.template at(p); + p += 8; + _ts = b.template at(p); + p += 8; + _id = b.template at(p); + p += 4; - _value = b.template at(p); p += 4; + _value = b.template at(p); + p += 4; - _issuedTo.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _issuedTo.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; if (b[p++] == 1) { - if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN; + } p += 2; - memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; } else { p += 2 + b.template at(p); } p += 2 + b.template at(p); - if (p > b.size()) + if (p > b.size()) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } return (p - startAt); } diff --git a/node/Topology.cpp b/node/Topology.cpp index 6a6675ab2..c2e5bb4b3 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -32,7 +32,8 @@ Topology::Topology(const RuntimeEnvironment *renv,void *tPtr) : { uint8_t tmp[ZT_WORLD_MAX_SERIALIZED_LENGTH]; uint64_t idtmp[2]; - idtmp[0] = 0; idtmp[1] = 0; + idtmp[0] = 0; + idtmp[1] = 0; int n = RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_PLANET,idtmp,tmp,sizeof(tmp)); if (n > 0) { try { @@ -55,8 +56,9 @@ Topology::~Topology() Hashtable< Address,SharedPtr >::Iterator i(_peers); Address *a = (Address *)0; SharedPtr *p = (SharedPtr *)0; - while (i.next(a,p)) + while (i.next(a,p)) { _savePeer((void *)0,*p); + } } SharedPtr Topology::addPeer(void *tPtr,const SharedPtr &peer) @@ -65,8 +67,9 @@ SharedPtr Topology::addPeer(void *tPtr,const SharedPtr &peer) { Mutex::Lock _l(_peers_m); SharedPtr &hp = _peers[peer->address()]; - if (!hp) + if (!hp) { hp = peer; + } np = hp; } return np; @@ -74,26 +77,31 @@ SharedPtr Topology::addPeer(void *tPtr,const SharedPtr &peer) SharedPtr Topology::getPeer(void *tPtr,const Address &zta) { - if (zta == RR->identity.address()) + if (zta == RR->identity.address()) { return SharedPtr(); + } { Mutex::Lock _l(_peers_m); const SharedPtr *const ap = _peers.get(zta); - if (ap) + if (ap) { return *ap; + } } try { Buffer buf; - uint64_t idbuf[2]; idbuf[0] = zta.toInt(); idbuf[1] = 0; + uint64_t idbuf[2]; + idbuf[0] = zta.toInt(); + idbuf[1] = 0; int len = RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_PEER,idbuf,buf.unsafeData(),ZT_PEER_MAX_SERIALIZED_STATE_SIZE); if (len > 0) { buf.setSize(len); Mutex::Lock _l(_peers_m); SharedPtr &ap = _peers[zta]; - if (ap) + if (ap) { return ap; + } ap = Peer::deserializeFromCache(RR->node->now(),tPtr,buf,RR); if (!ap) { _peers.erase(zta); @@ -112,8 +120,9 @@ Identity Topology::getIdentity(void *tPtr,const Address &zta) } else { Mutex::Lock _l(_peers_m); const SharedPtr *const ap = _peers.get(zta); - if (ap) + if (ap) { return (*ap)->identity(); + } } return Identity(); } @@ -138,8 +147,9 @@ SharedPtr Topology::getUpstreamPeer() } } - if (!best) + if (!best) { return SharedPtr(); + } return *best; } @@ -152,11 +162,13 @@ bool Topology::isUpstream(const Identity &id) const bool Topology::shouldAcceptWorldUpdateFrom(const Address &addr) const { Mutex::Lock _l(_upstreams_m); - if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),addr) != _upstreamAddresses.end()) + if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),addr) != _upstreamAddresses.end()) { return true; + } for(std::vector< std::pair< uint64_t,Address> >::const_iterator s(_moonSeeds.begin());s!=_moonSeeds.end();++s) { - if (s->second == addr) + if (s->second == addr) { return true; + } } return false; } @@ -166,8 +178,9 @@ ZT_PeerRole Topology::role(const Address &ztaddr) const Mutex::Lock _l(_upstreams_m); if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),ztaddr) != _upstreamAddresses.end()) { for(std::vector::const_iterator i(_planet.roots().begin());i!=_planet.roots().end();++i) { - if (i->identity.address() == ztaddr) + if (i->identity.address() == ztaddr) { return ZT_PEER_ROLE_PLANET; + } } return ZT_PEER_ROLE_MOON; } @@ -183,22 +196,26 @@ bool Topology::isProhibitedEndpoint(const Address &ztaddr,const InetAddress &ipa if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),ztaddr) != _upstreamAddresses.end()) { for(std::vector::const_iterator r(_planet.roots().begin());r!=_planet.roots().end();++r) { if (r->identity.address() == ztaddr) { - if (r->stableEndpoints.empty()) + if (r->stableEndpoints.empty()) { return false; // no stable endpoints specified, so allow dynamic paths + } for(std::vector::const_iterator e(r->stableEndpoints.begin());e!=r->stableEndpoints.end();++e) { - if (ipaddr.ipsEqual(*e)) + if (ipaddr.ipsEqual(*e)) { return false; + } } } } for(std::vector::const_iterator m(_moons.begin());m!=_moons.end();++m) { for(std::vector::const_iterator r(m->roots().begin());r!=m->roots().end();++r) { if (r->identity.address() == ztaddr) { - if (r->stableEndpoints.empty()) + if (r->stableEndpoints.empty()) { return false; // no stable endpoints specified, so allow dynamic paths + } for(std::vector::const_iterator e(r->stableEndpoints.begin());e!=r->stableEndpoints.end();++e) { - if (ipaddr.ipsEqual(*e)) + if (ipaddr.ipsEqual(*e)) { return false; + } } } } @@ -211,8 +228,9 @@ bool Topology::isProhibitedEndpoint(const Address &ztaddr,const InetAddress &ipa bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) { - if ((newWorld.type() != World::TYPE_PLANET)&&(newWorld.type() != World::TYPE_MOON)) + if ((newWorld.type() != World::TYPE_PLANET)&&(newWorld.type() != World::TYPE_MOON)) { return false; + } Mutex::Lock _l2(_peers_m); Mutex::Lock _l1(_upstreams_m); @@ -235,9 +253,11 @@ bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) } if (existing) { - if (existing->shouldBeReplacedBy(newWorld)) + if (existing->shouldBeReplacedBy(newWorld)) { *existing = newWorld; - else return false; + } else { + return false; + } } else if (newWorld.type() == World::TYPE_MOON) { if (alwaysAcceptNew) { _moons.push_back(newWorld); @@ -253,13 +273,15 @@ bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) break; } } - if (existing) + if (existing) { break; + } } } } - if (!existing) + if (!existing) { return false; + } } else { return false; } @@ -268,7 +290,8 @@ bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) Buffer sbuf; existing->serialize(sbuf,false); uint64_t idtmp[2]; - idtmp[0] = existing->id(); idtmp[1] = 0; + idtmp[0] = existing->id(); + idtmp[1] = 0; RR->node->stateObjectPut(tPtr,(existing->type() == World::TYPE_PLANET) ? ZT_STATE_OBJECT_PLANET : ZT_STATE_OBJECT_MOON,idtmp,sbuf.data(),sbuf.size()); } catch ( ... ) {} @@ -281,7 +304,8 @@ void Topology::addMoon(void *tPtr,const uint64_t id,const Address &seed) { char tmp[ZT_WORLD_MAX_SERIALIZED_LENGTH]; uint64_t idtmp[2]; - idtmp[0] = id; idtmp[1] = 0; + idtmp[0] = id; + idtmp[1] = 0; int n = RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_MOON,idtmp,tmp,sizeof(tmp)); if (n > 0) { try { @@ -296,8 +320,9 @@ void Topology::addMoon(void *tPtr,const uint64_t id,const Address &seed) if (seed) { Mutex::Lock _l(_upstreams_m); - if (std::find(_moonSeeds.begin(),_moonSeeds.end(),std::pair(id,seed)) == _moonSeeds.end()) + if (std::find(_moonSeeds.begin(),_moonSeeds.end(),std::pair(id,seed)) == _moonSeeds.end()) { _moonSeeds.push_back(std::pair(id,seed)); + } } } @@ -312,7 +337,8 @@ void Topology::removeMoon(void *tPtr,const uint64_t id) nm.push_back(*m); } else { uint64_t idtmp[2]; - idtmp[0] = id; idtmp[1] = 0; + idtmp[0] = id; + idtmp[1] = 0; RR->node->stateObjectDelete(tPtr,ZT_STATE_OBJECT_MOON,idtmp); } } @@ -320,8 +346,9 @@ void Topology::removeMoon(void *tPtr,const uint64_t id) std::vector< std::pair > cm; for(std::vector< std::pair >::const_iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) { - if (m->first != id) + if (m->first != id) { cm.push_back(*m); + } } _moonSeeds.swap(cm); @@ -350,8 +377,9 @@ void Topology::doPeriodicTasks(void *tPtr,int64_t now) Path::HashKey *k = (Path::HashKey *)0; SharedPtr *p = (SharedPtr *)0; while (i.next(k,p)) { - if (p->references() <= 1) + if (p->references() <= 1) { _paths.erase(*k); + } } } } @@ -382,8 +410,9 @@ void Topology::_memoizeUpstreams(void *tPtr) } else if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),i->identity.address()) == _upstreamAddresses.end()) { _upstreamAddresses.push_back(i->identity.address()); SharedPtr &hp = _peers[i->identity.address()]; - if (!hp) + if (!hp) { hp = new Peer(RR,RR->identity,i->identity); + } } } } @@ -396,7 +425,9 @@ void Topology::_savePeer(void *tPtr,const SharedPtr &peer) try { Buffer buf; peer->serializeForCache(buf); - uint64_t tmpid[2]; tmpid[0] = peer->address().toInt(); tmpid[1] = 0; + uint64_t tmpid[2]; + tmpid[0] = peer->address().toInt(); + tmpid[1] = 0; RR->node->stateObjectPut(tPtr,ZT_STATE_OBJECT_PEER,tmpid,buf.data(),buf.size()); } catch ( ... ) {} // sanity check, discard invalid entries } diff --git a/node/Topology.hpp b/node/Topology.hpp index 2d4e5d92c..248b11782 100644 --- a/node/Topology.hpp +++ b/node/Topology.hpp @@ -89,8 +89,9 @@ public: { Mutex::Lock _l(_peers_m); const SharedPtr *const ap = _peers.get(zta); - if (ap) + if (ap) { return *ap; + } return SharedPtr(); } @@ -105,8 +106,9 @@ public: { Mutex::Lock _l(_paths_m); SharedPtr &p = _paths[Path::HashKey(l,r)]; - if (!p) + if (!p) { p.set(new Path(l,r)); + } return p; } @@ -163,8 +165,9 @@ public: if (i->identity != RR->identity) { std::vector &ips = eps[i->identity.address()]; for(std::vector::const_iterator j(i->stableEndpoints.begin());j!=i->stableEndpoints.end();++j) { - if (std::find(ips.begin(),ips.end(),*j) == ips.end()) + if (std::find(ips.begin(),ips.end(),*j) == ips.end()) { ips.push_back(*j); + } } } } @@ -173,14 +176,16 @@ public: if (i->identity != RR->identity) { std::vector &ips = eps[i->identity.address()]; for(std::vector::const_iterator j(i->stableEndpoints.begin());j!=i->stableEndpoints.end();++j) { - if (std::find(ips.begin(),ips.end(),*j) == ips.end()) + if (std::find(ips.begin(),ips.end(),*j) == ips.end()) { ips.push_back(*j); + } } } } } - for(std::vector< std::pair >::const_iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) + for(std::vector< std::pair >::const_iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) { eps[m->second]; + } } /** @@ -209,8 +214,9 @@ public: Mutex::Lock _l(_upstreams_m); std::vector mw; for(std::vector< std::pair >::const_iterator s(_moonSeeds.begin());s!=_moonSeeds.end();++s) { - if (std::find(mw.begin(),mw.end(),s->first) == mw.end()) + if (std::find(mw.begin(),mw.end(),s->first) == mw.end()) { mw.push_back(s->first); + } } return mw; } @@ -287,8 +293,9 @@ public: SharedPtr *p = (SharedPtr *)0; while (i.next(a,p)) { const SharedPtr pp((*p)->getAppropriatePath(now,false)); - if (pp) + if (pp) { ++cnt; + } } return cnt; } @@ -354,8 +361,9 @@ public: inline unsigned int getOutboundPathMtu(const InetAddress &physicalAddress) { for(unsigned int i=0,j=_numConfiguredPhysicalPaths;i cpaths; - for(unsigned int i=0,j=_numConfiguredPhysicalPaths;i ZT_MAX_PHYSMTU) + } else if (pc.mtu > ZT_MAX_PHYSMTU) { pc.mtu = ZT_MAX_PHYSMTU; + } cpaths[*(reinterpret_cast(pathNetwork))] = pc; } else { diff --git a/node/Trace.cpp b/node/Trace.cpp index 8443a1217..8a9b7b284 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -59,38 +59,47 @@ void Trace::resettingPathsInScope(void *const tPtr,const Address &reporter,const d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_PHYADDR,myPhysicalAddress.toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__IP_SCOPE,(uint64_t)scope); - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); + } _spamToAllNetworks(tPtr,d,Trace::LEVEL_NORMAL); } void Trace::peerConfirmingUnknownPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &path,const uint64_t packetId,const Packet::Verb verb) { char tmp[128]; - if (!path) return; // sanity check + if (!path) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"trying unknown path %s to %.10llx (packet %.16llx verb %d local socket %lld network %.16llx)",path->address().toString(tmp),peer.address().toInt(),packetId,verb,path->localSocket(),networkId); std::pair byn; - if (networkId) { Mutex::Lock l(_byNet_m); _byNet.get(networkId,byn); } + if (networkId) { + Mutex::Lock l(_byNet_m); + _byNet.get(networkId,byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; d.add(ZT_REMOTE_TRACE_FIELD__EVENT,ZT_REMOTE_TRACE_EVENT__PEER_CONFIRMING_UNKNOWN_PATH_S); d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID,packetId); d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB,(uint64_t)verb); - if (networkId) + if (networkId) { d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID,networkId); + } d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR,peer.address()); if (path) { d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,path->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,path->localSocket()); } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } @@ -102,53 +111,69 @@ void Trace::bondStateMessage(void *const tPtr,char *msg) void Trace::peerLearnedNewPath(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &newPath,const uint64_t packetId) { char tmp[128]; - if (!newPath) return; // sanity check + if (!newPath) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"learned new path %s to %.10llx (packet %.16llx local socket %lld network %.16llx)",newPath->address().toString(tmp),peer.address().toInt(),packetId,newPath->localSocket(),networkId); std::pair byn; - if (networkId) { Mutex::Lock l(_byNet_m); _byNet.get(networkId,byn); } + if (networkId) { + Mutex::Lock l(_byNet_m); + _byNet.get(networkId,byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; d.add(ZT_REMOTE_TRACE_FIELD__EVENT,ZT_REMOTE_TRACE_EVENT__PEER_LEARNED_NEW_PATH_S); d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID,packetId); - if (networkId) - d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID,networkId); + if (networkId) { + d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, networkId); + } d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR,peer.address()); d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,newPath->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,newPath->localSocket()); - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::peerRedirected(void *const tPtr,const uint64_t networkId,Peer &peer,const SharedPtr &newPath) { char tmp[128]; - if (!newPath) return; // sanity check + if (!newPath) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"explicit redirect from %.10llx to path %s",peer.address().toInt(),newPath->address().toString(tmp)); std::pair byn; - if (networkId) { Mutex::Lock l(_byNet_m); _byNet.get(networkId,byn); } + if (networkId) { + Mutex::Lock l(_byNet_m); + _byNet.get(networkId,byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; d.add(ZT_REMOTE_TRACE_FIELD__EVENT,ZT_REMOTE_TRACE_EVENT__PEER_REDIRECTED_S); - if (networkId) + if (networkId) { d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID,networkId); + } d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR,peer.address()); d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,newPath->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,newPath->localSocket()); - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } @@ -157,7 +182,9 @@ void Trace::outgoingNetworkFrameDropped(void *const tPtr,const SharedPtr %s etherType %.4x size %u (%s)",network->id(),sourceMac.toString(tmp),destMac.toString(tmp2),etherType,frameLen,(reason) ? reason : "unknown reason"); @@ -173,20 +200,25 @@ void Trace::outgoingNetworkFrameDropped(void *const tPtr,const SharedPtr= (int)Trace::LEVEL_VERBOSE)) + if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,_globalTarget); - if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) + } + if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,byn.first); + } } } void Trace::incomingNetworkAccessDenied(void *const tPtr,const SharedPtr &network,const SharedPtr &path,const uint64_t packetId,const unsigned int packetLength,const Address &source,const Packet::Verb verb,bool credentialsRequested) { char tmp[128]; - if (!network) return; // sanity check + if (!network) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"%.16llx DENIED packet from %.10llx(%s) verb %d size %u%s",network->id(),source.toInt(),(path) ? (path->address().toString(tmp)) : "???",(int)verb,packetLength,credentialsRequested ? " (credentials requested)" : " (credentials not requested)"); @@ -205,17 +237,21 @@ void Trace::incomingNetworkAccessDenied(void *const tPtr,const SharedPtrid()); - if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) + if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,_globalTarget); - if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) + } + if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,byn.first); + } } } void Trace::incomingNetworkFrameDropped(void *const tPtr,const SharedPtr &network,const SharedPtr &path,const uint64_t packetId,const unsigned int packetLength,const Address &source,const Packet::Verb verb,const MAC &sourceMac,const MAC &destMac,const char *reason) { char tmp[128]; - if (!network) return; // sanity check + if (!network) { + return; // sanity check + } ZT_LOCAL_TRACE(tPtr,RR,"%.16llx DROPPED frame from %.10llx(%s) verb %d size %u",network->id(),source.toInt(),(path) ? (path->address().toString(tmp)) : "???",(int)verb,packetLength); @@ -235,13 +271,16 @@ void Trace::incomingNetworkFrameDropped(void *const tPtr,const SharedPtrid()); d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_MAC,sourceMac.toInt()); d.add(ZT_REMOTE_TRACE_FIELD__DEST_MAC,destMac.toInt()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) + if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,_globalTarget); - if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) + } + if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) { _send(tPtr,d,byn.first); + } } } @@ -261,8 +300,9 @@ void Trace::incomingPacketMessageAuthenticationFailure(void *const tPtr,const Sh d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,path->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,path->localSocket()); } - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } _send(tPtr,d,_globalTarget); } @@ -285,8 +325,9 @@ void Trace::incomingPacketInvalid(void *const tPtr,const SharedPtr &path,c d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,path->localSocket()); } d.add(ZT_REMOTE_TRACE_FIELD__PACKET_HOPS,(uint64_t)hops); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } _send(tPtr,d,_globalTarget); } @@ -307,8 +348,9 @@ void Trace::incomingPacketDroppedHELLO(void *const tPtr,const SharedPtr &p d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR,path->address().toString(tmp)); d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET,path->localSocket()); } - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } _send(tPtr,d,_globalTarget); } @@ -361,25 +403,33 @@ void Trace::networkFilter( d.add(ZT_REMOTE_TRACE_FIELD__FILTER_FLAG_INBOUND,inbound ? "1" : "0"); d.add(ZT_REMOTE_TRACE_FIELD__FILTER_RESULT,(int64_t)accept); d.add(ZT_REMOTE_TRACE_FIELD__FILTER_BASE_RULE_LOG,(const char *)primaryRuleSetLog.data(),(int)primaryRuleSetLog.sizeBytes()); - if (matchingCapabilityRuleSetLog) + if (matchingCapabilityRuleSetLog) { d.add(ZT_REMOTE_TRACE_FIELD__FILTER_CAP_RULE_LOG,(const char *)matchingCapabilityRuleSetLog->data(),(int)matchingCapabilityRuleSetLog->sizeBytes()); - if (matchingCapability) + } + if (matchingCapability) { d.add(ZT_REMOTE_TRACE_FIELD__FILTER_CAP_ID,(uint64_t)matchingCapability->id()); + } d.add(ZT_REMOTE_TRACE_FIELD__FRAME_LENGTH,(uint64_t)frameLen); - if (frameLen > 0) + if (frameLen > 0) { d.add(ZT_REMOTE_TRACE_FIELD__FRAME_DATA,(const char *)frameData,(frameLen > 256) ? (int)256 : (int)frameLen); + } - if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_RULES)) + if ((_globalTarget)&&((int)_globalLevel >= (int)Trace::LEVEL_RULES)) { _send(tPtr,d,_globalTarget); - if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_RULES)) + } + if ((byn.first)&&((int)byn.second >= (int)Trace::LEVEL_RULES)) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const CertificateOfMembership &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -389,20 +439,26 @@ void Trace::credentialRejected(void *const tPtr,const CertificateOfMembership &c d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID,(uint64_t)c.id()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP,c.timestamp()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO,c.issuedTo()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const CertificateOfOwnership &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -412,20 +468,26 @@ void Trace::credentialRejected(void *const tPtr,const CertificateOfOwnership &c, d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID,(uint64_t)c.id()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP,c.timestamp()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO,c.issuedTo()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const Capability &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -435,20 +497,26 @@ void Trace::credentialRejected(void *const tPtr,const Capability &c,const char * d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID,(uint64_t)c.id()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP,c.timestamp()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO,c.issuedTo()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const Tag &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -459,20 +527,26 @@ void Trace::credentialRejected(void *const tPtr,const Tag &c,const char *reason) d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP,c.timestamp()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO,c.issuedTo()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_INFO,(uint64_t)c.value()); - if (reason) + if (reason) { d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } void Trace::credentialRejected(void *const tPtr,const Revocation &c,const char *reason) { std::pair byn; - if (c.networkId()) { Mutex::Lock l(_byNet_m); _byNet.get(c.networkId(),byn); } + if (c.networkId()) { + Mutex::Lock l(_byNet_m); + _byNet.get(c.networkId(),byn); + } if ((_globalTarget)||(byn.first)) { Dictionary d; @@ -481,13 +555,16 @@ void Trace::credentialRejected(void *const tPtr,const Revocation &c,const char * d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE,(uint64_t)c.credentialType()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID,(uint64_t)c.id()); d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_REVOCATION_TARGET,c.target()); - if (reason) - d.add(ZT_REMOTE_TRACE_FIELD__REASON,reason); + if (reason) { + d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason); + } - if (_globalTarget) + if (_globalTarget) { _send(tPtr,d,_globalTarget); - if (byn.first) + } + if (byn.first) { _send(tPtr,d,byn.first); + } } } @@ -525,8 +602,9 @@ void Trace::_spamToAllNetworks(void *const tPtr,const Dictionary *v = (std::pair *)0; while (i.next(k,v)) { - if ((v)&&(v->first)&&((int)v->second >= (int)level)) + if ((v)&&(v->first)&&((int)v->second >= (int)level)) { _send(tPtr,d,v->first); + } } } diff --git a/node/Utils.cpp b/node/Utils.cpp index a0bf07077..be39aa0aa 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -188,18 +188,22 @@ const Utils::CPUIDRegisters Utils::CPUID; static void _Utils_doBurn(volatile uint8_t *ptr,unsigned int len) { volatile uint8_t *const end = ptr + len; - while (ptr != end) *(ptr++) = (uint8_t)0; + while (ptr != end) { + *(ptr++) = (uint8_t)0; + } } static void (*volatile _Utils_doBurn_ptr)(volatile uint8_t *,unsigned int) = _Utils_doBurn; void Utils::burn(void *ptr,unsigned int len) { (_Utils_doBurn_ptr)((volatile uint8_t *)ptr,len); } static unsigned long _Utils_itoa(unsigned long n,char *s) { - if (n == 0) + if (n == 0) { return 0; + } unsigned long pos = _Utils_itoa(n / 10,s); - if (pos >= 22) // sanity check, should be impossible + if (pos >= 22) { // sanity check, should be impossible pos = 22; + } s[pos] = '0' + (char)(n % 10); return pos + 1; } @@ -288,7 +292,9 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) exit(1); return; } - } else break; + } else { + break; + } } randomPtr = 0; s20.crypt12(randomBuf,randomBuf,sizeof(randomBuf)); diff --git a/node/Utils.hpp b/node/Utils.hpp index 685fdf591..d13674c1f 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -102,10 +102,17 @@ public: */ static inline unsigned int log2(uint32_t v) { - uint32_t r = (v > 0xffff) << 4; v >>= r; - uint32_t shift = (v > 0xff) << 3; v >>= shift; r |= shift; - shift = (v > 0xf) << 2; v >>= shift; r |= shift; - shift = (v > 0x3) << 1; v >>= shift; r |= shift; + uint32_t r = (v > 0xffff) << 4; + v >>= r; + uint32_t shift = (v > 0xff) << 3; + v >>= shift; + r |= shift; + shift = (v > 0xf) << 2; + v >>= shift; + r |= shift; + shift = (v > 0x3) << 1; + v >>= shift; + r |= shift; r |= (v >> 1); return (unsigned int)r; } @@ -121,8 +128,9 @@ public: static inline bool secureEq(const void *a,const void *b,unsigned int len) { uint8_t diff = 0; - for(unsigned int i=0;i(a))[i] ^ (reinterpret_cast(b))[i] ); + } return (diff == 0); } @@ -225,26 +233,32 @@ public: unsigned int l = 0; while (l < buflen) { uint8_t hc = *(reinterpret_cast(h++)); - if (!hc) break; + if (!hc) { + break; + } uint8_t c = 0; - if ((hc >= 48)&&(hc <= 57)) // 0..9 + if ((hc >= 48)&&(hc <= 57)) { // 0..9 c = hc - 48; - else if ((hc >= 97)&&(hc <= 102)) // a..f + } else if ((hc >= 97)&&(hc <= 102)) { // a..f c = hc - 87; - else if ((hc >= 65)&&(hc <= 70)) // A..F + } else if ((hc >= 65)&&(hc <= 70)) { // A..F c = hc - 55; + } hc = *(reinterpret_cast(h++)); - if (!hc) break; + if (!hc) { + break; + } c <<= 4; - if ((hc >= 48)&&(hc <= 57)) + if ((hc >= 48)&&(hc <= 57)) { c |= hc - 48; - else if ((hc >= 97)&&(hc <= 102)) + } else if ((hc >= 97)&&(hc <= 102)) { c |= hc - 87; - else if ((hc >= 65)&&(hc <= 70)) + } else if ((hc >= 65)&&(hc <= 70)) { c |= hc - 55; + } reinterpret_cast(buf)[l++] = c; } @@ -256,29 +270,39 @@ public: unsigned int l = 0; const char *hend = h + hlen; while (l < buflen) { - if (h == hend) break; + if (h == hend) { + break; + } uint8_t hc = *(reinterpret_cast(h++)); - if (!hc) break; + if (!hc) { + break; + } uint8_t c = 0; - if ((hc >= 48)&&(hc <= 57)) + if ((hc >= 48)&&(hc <= 57)) { c = hc - 48; - else if ((hc >= 97)&&(hc <= 102)) + } else if ((hc >= 97)&&(hc <= 102)) { c = hc - 87; - else if ((hc >= 65)&&(hc <= 70)) + } else if ((hc >= 65)&&(hc <= 70)) { c = hc - 55; + } - if (h == hend) break; + if (h == hend) { + break; + } hc = *(reinterpret_cast(h++)); - if (!hc) break; + if (!hc) { + break; + } c <<= 4; - if ((hc >= 48)&&(hc <= 57)) + if ((hc >= 48)&&(hc <= 57)) { c |= hc - 48; - else if ((hc >= 97)&&(hc <= 102)) + } else if ((hc >= 97)&&(hc <= 102)) { c |= hc - 87; - else if ((hc >= 65)&&(hc <= 70)) + } else if ((hc >= 65)&&(hc <= 70)) { c |= hc - 55; + } reinterpret_cast(buf)[l++] = c; } @@ -375,8 +399,9 @@ public: */ static inline bool scopy(char *dest,unsigned int len,const char *src) { - if (!len) + if (!len) { return false; // sanity check + } if (!src) { *dest = (char)0; return true; @@ -428,8 +453,9 @@ public: static inline bool isZero(const void *p,unsigned int len) { for(unsigned int i=0;i(&tmp)[i] = reinterpret_cast(p)[i]; + } return tmp; #else return *reinterpret_cast(p); @@ -672,8 +699,9 @@ public: static ZT_INLINE void storeMachineEndian(void *const p, const I i) noexcept { #ifdef ZT_NO_UNALIGNED_ACCESS - for(unsigned int k=0;k(p)[k] = reinterpret_cast(&i)[k]; + } #else *reinterpret_cast(p) = i; #endif diff --git a/node/World.hpp b/node/World.hpp index a8b80e0ca..a13d59a25 100644 --- a/node/World.hpp +++ b/node/World.hpp @@ -148,8 +148,9 @@ public: */ inline bool shouldBeReplacedBy(const World &update) { - if ((_id == 0)||(_type == TYPE_NULL)) + if ((_id == 0)||(_type == TYPE_NULL)) { return true; + } if ((_id == update._id)&&(_ts < update._ts)&&(_type == update._type)) { Buffer tmp; update.serialize(tmp,true); @@ -166,25 +167,32 @@ public: template inline void serialize(Buffer &b,bool forSign = false) const { - if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + if (forSign) { + b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } b.append((uint8_t)_type); b.append((uint64_t)_id); b.append((uint64_t)_ts); b.append(_updatesMustBeSignedBy.data,ZT_C25519_PUBLIC_KEY_LEN); - if (!forSign) + if (!forSign) { b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } b.append((uint8_t)_roots.size()); for(std::vector::const_iterator r(_roots.begin());r!=_roots.end();++r) { r->identity.serialize(b); b.append((uint8_t)r->stableEndpoints.size()); - for(std::vector::const_iterator ep(r->stableEndpoints.begin());ep!=r->stableEndpoints.end();++ep) + for(std::vector::const_iterator ep(r->stableEndpoints.begin());ep!=r->stableEndpoints.end();++ep) { ep->serialize(b); + } } - if (_type == TYPE_MOON) + if (_type == TYPE_MOON) { b.append((uint16_t)0); // no attached dictionary (for future use) + } - if (forSign) b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL); + if (forSign) { + b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL); + } } template @@ -195,34 +203,47 @@ public: _roots.clear(); switch((Type)b[p++]) { - case TYPE_NULL: _type = TYPE_NULL; break; // shouldn't ever really happen in serialized data but it's not invalid - case TYPE_PLANET: _type = TYPE_PLANET; break; - case TYPE_MOON: _type = TYPE_MOON; break; + case TYPE_NULL: // shouldn't ever really happen in serialized data but it's not invalid + _type = TYPE_NULL; + break; + case TYPE_PLANET: + _type = TYPE_PLANET; + break; + case TYPE_MOON: + _type = TYPE_MOON; + break; default: throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE; } - _id = b.template at(p); p += 8; - _ts = b.template at(p); p += 8; - memcpy(_updatesMustBeSignedBy.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); p += ZT_C25519_PUBLIC_KEY_LEN; - memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + _id = b.template at(p); + p += 8; + _ts = b.template at(p); + p += 8; + memcpy(_updatesMustBeSignedBy.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); + p += ZT_C25519_PUBLIC_KEY_LEN; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; const unsigned int numRoots = (unsigned int)b[p++]; - if (numRoots > ZT_WORLD_MAX_ROOTS) + if (numRoots > ZT_WORLD_MAX_ROOTS) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } for(unsigned int k=0;k ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT) + if (numStableEndpoints > ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT) { throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; + } for(unsigned int kk=0;kk(p) + 2; + } return (p - startAt); } diff --git a/osdep/BSDEthernetTap.cpp b/osdep/BSDEthernetTap.cpp index efba03147..b2e1a8760 100644 --- a/osdep/BSDEthernetTap.cpp +++ b/osdep/BSDEthernetTap.cpp @@ -74,7 +74,8 @@ BSDEthernetTap::BSDEthernetTap( _mtu(mtu), _metric(metric), _fd(0), - _enabled(true) + _enabled(true), + _lastIfAddrsUpdate(0) { static Mutex globalTapCreateLock; char devpath[64],ethaddr[64],mtustr[32],metstr[32],tmpdevname[32]; @@ -287,6 +288,13 @@ bool BSDEthernetTap::removeIp(const InetAddress &ip) std::vector BSDEthernetTap::ips() const { + uint64_t now = OSUtils::now(); + + if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { + return _ifaddrs; + } + _lastIfAddrsUpdate = now; + struct ifaddrs *ifa = (struct ifaddrs *)0; if (getifaddrs(&ifa)) return std::vector(); @@ -320,6 +328,8 @@ std::vector BSDEthernetTap::ips() const std::sort(r.begin(),r.end()); std::unique(r.begin(),r.end()); + _ifaddrs = r; + return r; } diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp index c66fb6f79..fc4e4908e 100644 --- a/osdep/BSDEthernetTap.hpp +++ b/osdep/BSDEthernetTap.hpp @@ -71,6 +71,8 @@ private: int _fd; int _shutdownSignalPipe[2]; volatile bool _enabled; + mutable std::vector _ifaddrs; + mutable uint64_t _lastIfAddrsUpdate; }; } // namespace ZeroTier diff --git a/osdep/BlockingQueue.hpp b/osdep/BlockingQueue.hpp index cce37a04a..c3695e708 100644 --- a/osdep/BlockingQueue.hpp +++ b/osdep/BlockingQueue.hpp @@ -116,6 +116,10 @@ public: return OK; } + inline size_t size() const { + return q.size(); + } + private: std::queue q; mutable std::mutex m; diff --git a/osdep/EthernetTap.hpp b/osdep/EthernetTap.hpp index 43beb440d..893e70c34 100644 --- a/osdep/EthernetTap.hpp +++ b/osdep/EthernetTap.hpp @@ -23,6 +23,8 @@ #include #include +#define GETIFADDRS_CACHE_TIME 1000 + namespace ZeroTier { class EthernetTap diff --git a/osdep/LinuxEthernetTap.cpp b/osdep/LinuxEthernetTap.cpp index 8995b102b..a888db9d9 100644 --- a/osdep/LinuxEthernetTap.cpp +++ b/osdep/LinuxEthernetTap.cpp @@ -126,12 +126,14 @@ LinuxEthernetTap::LinuxEthernetTap( _mtu(mtu), _fd(0), _enabled(true), - _run(true) + _run(true), + _lastIfAddrsUpdate(0) { static std::mutex s_tapCreateLock; char procpath[128],nwids[32]; struct stat sbuf; + // Create only one tap at a time globally. std::lock_guard tapCreateLock(s_tapCreateLock); @@ -438,6 +440,14 @@ bool LinuxEthernetTap::removeIp(const InetAddress &ip) std::vector LinuxEthernetTap::ips() const { + + uint64_t now = OSUtils::now(); + + if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { + return _ifaddrs; + } + _lastIfAddrsUpdate = now; + struct ifaddrs *ifa = (struct ifaddrs *)0; if (getifaddrs(&ifa)) return std::vector(); @@ -471,6 +481,8 @@ std::vector LinuxEthernetTap::ips() const std::sort(r.begin(),r.end()); r.erase(std::unique(r.begin(),r.end()),r.end()); + _ifaddrs = r; + return r; } diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp index 3603740d4..424a6d37e 100644 --- a/osdep/LinuxEthernetTap.hpp +++ b/osdep/LinuxEthernetTap.hpp @@ -57,6 +57,9 @@ public: virtual void setMtu(unsigned int mtu); virtual void setDns(const char *domain, const std::vector &servers) {} + + + private: void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); void *_arg; @@ -71,6 +74,8 @@ private: std::atomic_bool _enabled; std::atomic_bool _run; std::thread _tapReaderThread; + mutable std::vector _ifaddrs; + mutable uint64_t _lastIfAddrsUpdate; }; } // namespace ZeroTier diff --git a/osdep/MacDNSHelper.mm b/osdep/MacDNSHelper.mm index 8a5bebd92..5d6d14927 100644 --- a/osdep/MacDNSHelper.mm +++ b/osdep/MacDNSHelper.mm @@ -122,21 +122,16 @@ bool MacDNSHelper::addIps(uint64_t nwid, const MAC mac, const char *dev, const s CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8); - CFStringRef* cfaddrs = new CFStringRef[1]; - CFStringRef* cfprefixes = new CFStringRef[1]; - CFStringRef* cfdestaddrs = new CFStringRef[1]; - CFStringRef* cfflags = new CFStringRef[1]; + CFStringRef cfaddr = CFStringCreateWithCString(NULL, llStr, kCFStringEncodingUTF8); + CFStringRef cfprefixes = CFStringCreateWithCString(NULL, "64", kCFStringEncodingUTF8); + CFStringRef cfdestaddrs = CFStringCreateWithCString(NULL, "::ffff:ffff:ffff:ffff:0:0", kCFStringEncodingUTF8); + CFStringRef cfflags = CFStringCreateWithCString(NULL, "0", kCFStringEncodingUTF8); - cfaddrs[0] = CFStringCreateWithCString(NULL, llStr, kCFStringEncodingUTF8); - cfprefixes[0] = CFStringCreateWithCString(NULL, "64", kCFStringEncodingUTF8); - cfdestaddrs[0] = CFStringCreateWithCString(NULL, "::ffff:ffff:ffff:ffff:0:0", kCFStringEncodingUTF8); - cfflags[0] = CFStringCreateWithCString(NULL, "0", kCFStringEncodingUTF8); - - CFArrayRef addrArray = CFArrayCreate(NULL, (const void**)cfaddrs, 1, &kCFTypeArrayCallBacks); - CFArrayRef prefixArray = CFArrayCreate(NULL, (const void**)cfprefixes, 1, &kCFTypeArrayCallBacks); - CFArrayRef destArray = CFArrayCreate(NULL, (const void**)cfdestaddrs, 1, &kCFTypeArrayCallBacks); - CFArrayRef flagsArray = CFArrayCreate(NULL, (const void**)cfflags, 1, &kCFTypeArrayCallBacks); + CFArrayRef addrArray = CFArrayCreate(NULL, (const void**)&cfaddr, 1, &kCFTypeArrayCallBacks); + CFArrayRef prefixArray = CFArrayCreate(NULL, (const void**)&cfprefixes, 1, &kCFTypeArrayCallBacks); + CFArrayRef destArray = CFArrayCreate(NULL, (const void**)&cfdestaddrs, 1, &kCFTypeArrayCallBacks); + CFArrayRef flagsArray = CFArrayCreate(NULL, (const void**)&cfflags, 1, &kCFTypeArrayCallBacks); CFStringRef cfdev = CFStringCreateWithCString(NULL, dev, kCFStringEncodingUTF8); const int SIZE = 5; @@ -182,6 +177,14 @@ bool MacDNSHelper::addIps(uint64_t nwid, const MAC mac, const char *dev, const s fprintf(stderr, "Error writing IPv6 configuration\n"); } } + if (oldAddrs != NULL) { + CFRelease(oldAddrs); + } + + CFRelease(cfaddr); + CFRelease(cfprefixes); + CFRelease(cfdestaddrs); + CFRelease(cfflags); CFRelease(addrArray); CFRelease(prefixArray); @@ -189,16 +192,15 @@ bool MacDNSHelper::addIps(uint64_t nwid, const MAC mac, const char *dev, const s CFRelease(flagsArray); CFRelease(cfdev); - CFRelease(list); - CFRelease(dict); - CFRelease(ds); CFRelease(key); - delete[] cfaddrs; - delete[] cfprefixes; - delete[] cfdestaddrs; - delete[] cfflags; + // for (unsigned int i = 0; i < SIZE; ++i) { + // values[i] = NULL; + // } + + CFRelease(list); + CFRelease(dict); return ret; } diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp index 55822fd5a..392f222b8 100644 --- a/osdep/MacEthernetTap.cpp +++ b/osdep/MacEthernetTap.cpp @@ -90,7 +90,8 @@ MacEthernetTap::MacEthernetTap( _agentStdout2(-1), _agentStderr2(-1), _agentPid(-1), - _enabled(true) + _enabled(true), + _lastIfAddrsUpdate(0) { char ethaddr[64],mtustr[16],devnostr[16],devstr[16],metricstr[16]; OSUtils::ztsnprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); @@ -341,8 +342,16 @@ bool MacEthernetTap::removeIp(const InetAddress &ip) std::vector MacEthernetTap::ips() const { + uint64_t now = OSUtils::now(); + + if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { + return _ifaddrs; + } + _lastIfAddrsUpdate = now; + struct ifaddrs *ifa = (struct ifaddrs *)0; std::vector r; + if (!getifaddrs(&ifa)) { struct ifaddrs *p = ifa; while (p) { @@ -368,6 +377,9 @@ std::vector MacEthernetTap::ips() const } std::sort(r.begin(),r.end()); r.erase(std::unique(r.begin(),r.end()),r.end()); + + _ifaddrs = r; + return r; } diff --git a/osdep/MacEthernetTap.hpp b/osdep/MacEthernetTap.hpp index 4b02999b9..8ba378022 100644 --- a/osdep/MacEthernetTap.hpp +++ b/osdep/MacEthernetTap.hpp @@ -77,6 +77,9 @@ private: int _agentStdin,_agentStdout,_agentStderr,_agentStdin2,_agentStdout2,_agentStderr2; long _agentPid; volatile bool _enabled; + mutable std::vector _ifaddrs; + mutable uint64_t _lastIfAddrsUpdate; + }; } // namespace ZeroTier diff --git a/osdep/ManagedRoute.cpp b/osdep/ManagedRoute.cpp index a8f996839..36d1f07a2 100644 --- a/osdep/ManagedRoute.cpp +++ b/osdep/ManagedRoute.cpp @@ -509,13 +509,13 @@ bool ManagedRoute::sync() } } - //if (!_applied.count(leftt)) { + if (leftt && !_applied.count(leftt)) { _applied[leftt] = !_via; //_routeCmd("delete",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); _routeCmd("add",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); //_routeCmd("change",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); - //} - if (rightt) { + } + if (rightt && !_applied.count(rightt)) { _applied[rightt] = !_via; //_routeCmd("delete",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device); _routeCmd("add",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device); diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index e237325c4..67536c56a 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "../node/Constants.hpp" #include "../node/Utils.hpp" @@ -66,6 +67,18 @@ unsigned int OSUtils::ztsnprintf(char *buf,unsigned int len,const char *fmt,...) return (unsigned int)n; } +std::string OSUtils::networkIDStr(const uint64_t nwid) { + char tmp[32] = {}; + ztsnprintf(tmp, sizeof(tmp), "%.16" PRIx64, nwid); + return std::string(tmp); +} + +std::string OSUtils::nodeIDStr(const uint64_t nid) { + char tmp[32] = {}; + ztsnprintf(tmp, sizeof(tmp), "%.10" PRIx64, nid); + return std::string(tmp); +} + #ifdef __UNIX_LIKE__ bool OSUtils::redirectUnixOutputs(const char *stdoutPath,const char *stderrPath) throw() diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp index 021b3876f..43df98cb8 100644 --- a/osdep/OSUtils.hpp +++ b/osdep/OSUtils.hpp @@ -68,6 +68,22 @@ public: */ static unsigned int ztsnprintf(char *buf,unsigned int len,const char *fmt,...); + /** + * Converts a uint64_t network ID into a string + * + * @param nwid network ID + * @throws std::length_error buf[] too short (buf[] will still be left null-terminated) + */ + static std::string networkIDStr(const uint64_t nwid); + + /** + * Converts a uint64_t node ID into a string + * + * @param nid node ID + * @throws std::length_error buf[] too short (buf[] will still be left null-terminated) + */ + static std::string nodeIDStr(const uint64_t nid); + #ifdef __UNIX_LIKE__ /** * Close STDOUT_FILENO and STDERR_FILENO and replace them with output to given path diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp index 54f9b7f8b..4a71629cc 100644 --- a/osdep/Phy.hpp +++ b/osdep/Phy.hpp @@ -453,15 +453,33 @@ public: inline bool udpSend(PhySocket *sock,const struct sockaddr *remoteAddress,const void *data,unsigned long len) { PhySocketImpl &sws = *(reinterpret_cast(sock)); + bool sent = false; #if defined(_WIN32) || defined(_WIN64) - int sent = ((long)::sendto(sws.sock,reinterpret_cast(data),len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len); - Metrics::udp_send += sent; - return sent; + sent = ((long)::sendto( + sws.sock, + reinterpret_cast(data), + len, + 0, + remoteAddress, + (remoteAddress->sa_family == AF_INET6) ? + sizeof(struct sockaddr_in6) : + sizeof(struct sockaddr_in)) == (long)len); #else - ssize_t sent = ((long)::sendto(sws.sock,data,len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len); - Metrics::udp_send += sent; - return sent; + sent = ((long)::sendto( + sws.sock, + data, + len, + 0, + remoteAddress, + (remoteAddress->sa_family == AF_INET6) ? + sizeof(struct sockaddr_in6) : + sizeof(struct sockaddr_in)) == (long)len); #endif + if (sent) { + Metrics::udp_send += len; + } + + return sent; } #ifdef __UNIX_LIKE__ diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index db65bbf41..7a9d4b309 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -467,7 +467,8 @@ WindowsEthernetTap::WindowsEthernetTap( _pathToHelpers(hp), _run(true), _initialized(false), - _enabled(true) + _enabled(true), + _lastIfAddrsUpdate(0) { char subkeyName[1024]; char subkeyClass[1024]; @@ -749,6 +750,14 @@ std::vector WindowsEthernetTap::ips() const if (!_initialized) return addrs; + uint64_t now = OSUtils::now(); + + if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { + return _ifaddrs; + } + + _lastIfAddrsUpdate = now; + try { MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { @@ -777,6 +786,8 @@ std::vector WindowsEthernetTap::ips() const std::sort(addrs.begin(),addrs.end()); addrs.erase(std::unique(addrs.begin(),addrs.end()),addrs.end()); + _ifaddrs = addrs; + return addrs; } diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index a08042d44..a79dac0ec 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -152,6 +152,9 @@ private: volatile bool _run; volatile bool _initialized; volatile bool _enabled; + + mutable std::vector _ifaddrs; + mutable uint64_t _lastIfAddrsUpdate; }; } // namespace ZeroTier diff --git a/service/OneService.cpp b/service/OneService.cpp index 2d3cfdf2e..683db6e24 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -11,6 +11,7 @@ */ /****/ +#include #include #include #include @@ -53,6 +54,8 @@ #include "OneService.hpp" #include "SoftwareUpdater.hpp" +#include + #if ZT_SSO_ENABLED #include #endif @@ -198,6 +201,58 @@ std::string ssoResponseTemplate = R"""( )"""; +#if ZT_DEBUG==1 +std::string dump_headers(const httplib::Headers &headers) { + std::string s; + char buf[BUFSIZ]; + + for (auto it = headers.begin(); it != headers.end(); ++it) { + const auto &x = *it; + snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str()); + s += buf; + } + + return s; +} + +std::string http_log(const httplib::Request &req, const httplib::Response &res) { + std::string s; + char buf[BUFSIZ]; + + s += "================================\n"; + + snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(), + req.version.c_str(), req.path.c_str()); + s += buf; + + std::string query; + for (auto it = req.params.begin(); it != req.params.end(); ++it) { + const auto &x = *it; + snprintf(buf, sizeof(buf), "%c%s=%s", + (it == req.params.begin()) ? '?' : '&', x.first.c_str(), + x.second.c_str()); + query += buf; + } + snprintf(buf, sizeof(buf), "%s\n", query.c_str()); + s += buf; + + s += dump_headers(req.headers); + + s += "--------------------------------\n"; + + snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str()); + s += buf; + s += dump_headers(res.headers); + s += "\n"; + + if (!res.body.empty()) { s += res.body; } + + s += "\n"; + + return s; +} +#endif + // Configured networks class NetworkState { @@ -351,15 +406,6 @@ public: return _managedRoutes; } - const char* getAuthURL() { -#if ZT_SSO_ENABLED - if (_idc != nullptr) { - return zeroidc::zeroidc_get_auth_url(_idc); - } - fprintf(stderr, "_idc is null\n"); -#endif - return ""; - } char* doTokenExchange(const char *code) { char *ret = nullptr; @@ -517,7 +563,7 @@ static void _networkToJson(nlohmann::json &nj,NetworkState &ns) } nj["dns"] = m; if (ns.config().ssoEnabled) { - const char* authURL = ns.getAuthURL(); + const char* authURL = ns.config().authenticationURL; //fprintf(stderr, "Auth URL: %s\n", authURL); nj["authenticationURL"] = authURL; nj["authenticationExpiryTime"] = (ns.getExpiryTime()*1000); @@ -715,10 +761,12 @@ public: Phy _phy; Node *_node; SoftwareUpdater *_updater; - PhySocket *_localControlSocket4; - PhySocket *_localControlSocket6; bool _updateAutoApply; - bool _allowTcpFallbackRelay; + + httplib::Server _controlPlane; + std::thread _serverThread; + + bool _allowTcpFallbackRelay; bool _forceTcpRelay; bool _allowSecondaryPort; @@ -815,9 +863,9 @@ public: ,_phy(this,false,true) ,_node((Node *)0) ,_updater((SoftwareUpdater *)0) - ,_localControlSocket4((PhySocket *)0) - ,_localControlSocket6((PhySocket *)0) ,_updateAutoApply(false) + ,_controlPlane() + ,_serverThread() ,_forceTcpRelay(false) ,_primaryPort(port) ,_udpPortPickerCounter(0) @@ -863,13 +911,13 @@ public: WinFWHelper::removeICMPRules(); #endif _binder.closeAll(_phy); - _phy.close(_localControlSocket4); - _phy.close(_localControlSocket6); #if ZT_VAULT_SUPPORT curl_global_cleanup(); #endif + _controlPlane.stop(); + _serverThread.join(); #ifdef ZT_USE_MINIUPNPC @@ -945,22 +993,6 @@ public: return _termReason; } - // Bind TCP control socket to 127.0.0.1 and ::1 as well for loopback TCP control socket queries - { - struct sockaddr_in lo4; - memset(&lo4,0,sizeof(lo4)); - lo4.sin_family = AF_INET; - lo4.sin_addr.s_addr = Utils::hton((uint32_t)0x7f000001); - lo4.sin_port = Utils::hton((uint16_t)_ports[0]); - _localControlSocket4 = _phy.tcpListen((const struct sockaddr *)&lo4); - struct sockaddr_in6 lo6; - memset(&lo6,0,sizeof(lo6)); - lo6.sin6_family = AF_INET6; - lo6.sin6_addr.s6_addr[15] = 1; - lo6.sin6_port = lo4.sin_port; - _localControlSocket6 = _phy.tcpListen((const struct sockaddr *)&lo6); - } - // Save primary port to a file so CLIs and GUIs can learn it easily char portstr[64]; OSUtils::ztsnprintf(portstr,sizeof(portstr),"%u",_ports[0]); @@ -1013,6 +1045,8 @@ public: } _node->setNetconfMaster((void *)_controller); + startHTTPControlPlane(); + // Join existing networks in networks.d { std::vector networksDotD(OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S "networks.d").c_str())); @@ -1402,559 +1436,574 @@ public: return true; } - // ========================================================================= - // Internal implementation methods for control plane, route setup, etc. - // ========================================================================= + // Internal HTTP Control Plane + void startHTTPControlPlane() { + std::vector noAuthEndpoints { "/sso", "/health" }; - inline unsigned int handleControlPlaneHttpRequest( - const InetAddress &fromAddress, - unsigned int httpMethod, - const std::string &path, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) - { - char tmp[256]; - unsigned int scode = 404; - json res; - std::vector ps(OSUtils::split(path.c_str(),"/","","")); - std::map urlArgs; - - /* Note: this is kind of restricted in what it'll take. It does not support - * URL encoding, and /'s in URL args will screw it up. But the only URL args - * it really uses in ?jsonp=functionName, and otherwise it just takes simple - * paths to simply-named resources. */ - if (!ps.empty()) { - std::size_t qpos = ps[ps.size() - 1].find('?'); - if (qpos != std::string::npos) { - std::string args(ps[ps.size() - 1].substr(qpos + 1)); - ps[ps.size() - 1] = ps[ps.size() - 1].substr(0,qpos); - std::vector asplit(OSUtils::split(args.c_str(),"&","","")); - for(std::vector::iterator a(asplit.begin());a!=asplit.end();++a) { - std::size_t eqpos = a->find('='); - if (eqpos == std::string::npos) - urlArgs[*a] = ""; - else urlArgs[a->substr(0,eqpos)] = a->substr(eqpos + 1); + auto setContent = [=] (const httplib::Request &req, httplib::Response &res, std::string content) { + if (req.has_param("jsonp")) { + if (content.length() > 0) { + res.set_content(req.get_param_value("jsonp") + "(" + content + ");", "application/javascript"); + } else { + res.set_content(req.get_param_value("jsonp") + "(null);", "application/javascript"); } - } - } else { - return 404; - } - - bool isAuth = false; - { - std::map::const_iterator ah(headers.find("x-zt1-auth")); - if ((ah != headers.end())&&(_authToken == ah->second)) { - isAuth = true; } else { - ah = urlArgs.find("auth"); - if ((ah != urlArgs.end())&&(_authToken == ah->second)) - isAuth = true; + if (content.length() > 0) { + res.set_content(content, "application/json"); + } else { + res.set_content("{}", "application/json"); + } } - } + }; -#ifdef __SYNOLOGY__ - // Authenticate via Synology's built-in cgi script - if (!isAuth) { - int synotoken_pos = path.find("SynoToken"); - int argpos = path.find('?'); - if(synotoken_pos != std::string::npos && argpos != std::string::npos) { - std::string cookie = path.substr(argpos+1, synotoken_pos-(argpos+1)); - std::string synotoken = path.substr(synotoken_pos); - std::string cookie_val = cookie.substr(cookie.find('=')+1); - std::string synotoken_val = synotoken.substr(synotoken.find('=')+1); - // Set necessary env for auth script - std::map::const_iterator ah2(headers.find("x-forwarded-for")); - setenv("HTTP_COOKIE", cookie_val.c_str(), true); - setenv("HTTP_X_SYNO_TOKEN", synotoken_val.c_str(), true); - setenv("REMOTE_ADDR", ah2->second.c_str(),true); - char user[256], buf[1024]; - FILE *fp = NULL; - bzero(user, 256); - fp = popen("/usr/syno/synoman/webman/modules/authenticate.cgi", "r"); - if(!fp) - isAuth = false; - else { - bzero(buf, sizeof(buf)); - fread(buf, 1024, 1, fp); - if(strlen(buf) > 0) { - snprintf(user, 256, "%s", buf); - isAuth = true; + + auto authCheck = [=] (const httplib::Request &req, httplib::Response &res) { + std::string r = req.remote_addr; + InetAddress remoteAddr(r.c_str()); + + bool ipAllowed = false; + bool isAuth = false; + // If localhost, allow + if (remoteAddr.ipScope() == InetAddress::IP_SCOPE_LOOPBACK) { + ipAllowed = true; + } + + if (!ipAllowed) { + for (auto i = _allowManagementFrom.begin(); i != _allowManagementFrom.end(); ++i) { + if (i->containsAddress(remoteAddr)) { + ipAllowed = true; + break; + } + } + } + + + if (ipAllowed) { + // auto-pass endpoints in `noAuthEndpoints`. No auth token required + if (std::find(noAuthEndpoints.begin(), noAuthEndpoints.end(), req.path) != noAuthEndpoints.end()) { + isAuth = true; + } + + if (!isAuth) { + // check auth token + if (req.has_header("x-zt1-auth")) { + std::string token = req.get_header_value("x-zt1-auth"); + if (token == _authToken) { + isAuth = true; + } + } else if (req.has_param("auth")) { + std::string token = req.get_param_value("auth"); + if (token == _authToken) { + isAuth = true; + } + } + } + } + + if (ipAllowed && isAuth) { + return httplib::Server::HandlerResponse::Unhandled; + } + setContent(req, res, "{}"); + res.status = 401; + return httplib::Server::HandlerResponse::Handled; + }; + + + + _controlPlane.Get("/bond/show/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + if (!_node->bondController()->inUse()) { + setContent(req, res, ""); + res.status = 400; + return; + } + + ZT_PeerList *pl = _node->peers(); + if (pl) { + auto id = req.matches[1]; + auto out = json::object(); + uint64_t wantp = Utils::hexStrToU64(id.str().c_str()); + for(unsigned long i=0;ipeerCount;++i) { + if (pl->peers[i].address == wantp) { + SharedPtr bond = _node->bondController()->getBondByPeerId(wantp); + if (bond) { + _peerToJson(out,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); + setContent(req, res, out.dump()); + } else { + setContent(req, res, ""); + res.status = 400; + } } } - pclose(fp); } - } -#endif - if (httpMethod == HTTP_GET) { - if (isAuth) { - if (ps[0] == "bond") { - if (_node->bondController()->inUse()) { - if (ps.size() == 3) { - if (ps[2].length() == 10) { - // check if hex string + _node->freeQueryResult((void *)pl); + }); - ZT_PeerList *pl = _node->peers(); - if (pl) { - uint64_t wantp = Utils::hexStrToU64(ps[2].c_str()); - for(unsigned long i=0;ipeerCount;++i) { - if (pl->peers[i].address == wantp) { - if (ps[1] == "show") { - SharedPtr bond = _node->bondController()->getBondByPeerId(wantp); - if (bond) { - _peerToJson(res,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); - scode = 200; - } else { - scode = 400; - } - } - } - } - } - _node->freeQueryResult((void *)pl); - } - } - } else { - scode = 400; /* bond controller is not enabled */ - } - } else if (ps[0] == "config") { - Mutex::Lock lc(_localConfig_m); - res = _localConfig; - scode = 200; - } else if (ps[0] == "status") { - ZT_NodeStatus status; - _node->status(&status); + auto bondRotate = [&, setContent](const httplib::Request &req, httplib::Response &res) { + if (!_node->bondController()->inUse()) { + setContent(req, res, ""); + res.status = 400; + return; + } - OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",status.address); - res["address"] = tmp; - res["publicIdentity"] = status.publicIdentity; - res["online"] = (bool)(status.online != 0); - res["tcpFallbackActive"] = (_tcpFallbackTunnel != (TcpConnection *)0); - res["versionMajor"] = ZEROTIER_ONE_VERSION_MAJOR; - res["versionMinor"] = ZEROTIER_ONE_VERSION_MINOR; - res["versionRev"] = ZEROTIER_ONE_VERSION_REVISION; - res["versionBuild"] = ZEROTIER_ONE_VERSION_BUILD; - OSUtils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); - res["version"] = tmp; - res["clock"] = OSUtils::now(); + auto bondID = req.matches[1]; + uint64_t id = Utils::hexStrToU64(bondID.str().c_str()); - { - Mutex::Lock _l(_localConfig_m); - res["config"] = _localConfig; + exit(0); + SharedPtr bond = _node->bondController()->getBondByPeerId(id); + if (bond) { + if (bond->abForciblyRotateLink()) { + res.status = 200; + } else { + res.status = 400; + } + } else { + fprintf(stderr, "unable to find bond to peer %llx\n", (unsigned long long)id); + res.status = 400; + } + setContent(req, res, "{}"); + }; + _controlPlane.Post("/bond/rotate/([0-9a-fA-F]{10})", bondRotate); + _controlPlane.Put("/bond/rotate/([0-9a-fA-F]{10})", bondRotate); + + _controlPlane.Get("/config", [&, setContent](const httplib::Request &req, httplib::Response &res) { + std::string config; + { + Mutex::Lock lc(_localConfig_m); + config = _localConfig.dump(); + } + if (config == "null") { + config = "{}"; + } + setContent(req, res, config); + }); + + auto configPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { + json j(OSUtils::jsonParse(req.body)); + if (j.is_object()) { + Mutex::Lock lcl(_localConfig_m); + json lc(_localConfig); + for(json::const_iterator s(j.begin()); s != j.end(); ++s) { + lc["settings"][s.key()] = s.value(); + } + std::string lcStr = OSUtils::jsonDump(lc, 4); + if (OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S "local.conf").c_str(), lcStr)) { + _localConfig = lc; + } + } + setContent(req, res, "{}"); + }; + _controlPlane.Post("/config/settings", configPost); + _controlPlane.Put("/config/settings", configPost); + + _controlPlane.Get("/health", [&, setContent](const httplib::Request &req, httplib::Response &res) { + json out = json::object(); + + char tmp[256]; + + ZT_NodeStatus status; + _node->status(&status); + + out["online"] = (bool)(status.online != 0); + out["versionMajor"] = ZEROTIER_ONE_VERSION_MAJOR; + out["versionMinor"] = ZEROTIER_ONE_VERSION_MINOR; + out["versionRev"] = ZEROTIER_ONE_VERSION_REVISION; + out["versionBuild"] = ZEROTIER_ONE_VERSION_BUILD; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + out["version"] = tmp; + out["clock"] = OSUtils::now(); + + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/moon", [&, setContent](const httplib::Request &req, httplib::Response &res) { + std::vector moons(_node->moons()); + + auto out = json::array(); + for (auto i = moons.begin(); i != moons.end(); ++i) { + json mj; + _moonToJson(mj, *i); + out.push_back(mj); + } + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/moon/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res){ + std::vector moons(_node->moons()); + auto input = req.matches[1]; + auto out = json::object(); + const uint64_t id = Utils::hexStrToU64(input.str().c_str()); + for (auto i = moons.begin(); i != moons.end(); ++i) { + if (i->id() == id) { + _moonToJson(out, *i); + break; + } + } + setContent(req, res, out.dump()); + }); + + auto moonPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto input = req.matches[1]; + uint64_t seed = 0; + try { + json j(OSUtils::jsonParse(req.body)); + if (j.is_object()) { + seed = Utils::hexStrToU64(OSUtils::jsonString(j["seed"],"0").c_str()); + } + } catch ( ... ) { + // discard invalid JSON + } + + std::vector moons(_node->moons()); + const uint64_t id = Utils::hexStrToU64(input.str().c_str()); + bool found = false; + auto out = json::object(); + for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { + if (m->id() == id) { + _moonToJson(out,*m); + found = true; + break; + } + } + + if (!found && seed != 0) { + char tmp[64]; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",id); + out["id"] = tmp; + out["roots"] = json::array(); + out["timestamp"] = 0; + out["signature"] = json(); + out["updatesMustBeSignedBy"] = json(); + out["waiting"] = true; + _node->orbit((void *)0,id,seed); + } + setContent(req, res, out.dump()); + }; + _controlPlane.Post("/moon/([0-9a-fA-F]{10})", moonPost); + _controlPlane.Put("/moon/([0-9a-fA-F]{10})", moonPost); + + _controlPlane.Delete("/moon/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto input = req.matches[1]; + uint64_t id = Utils::hexStrToU64(input.str().c_str()); + auto out = json::object(); + _node->deorbit((void*)0,id); + out["result"] = true; + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/network", [&, setContent](const httplib::Request &req, httplib::Response &res) { + Mutex::Lock _l(_nets_m); + auto out = json::array(); + + for (auto it = _nets.begin(); it != _nets.end(); ++it) { + NetworkState &ns = it->second; + json nj; + _networkToJson(nj, ns); + out.push_back(nj); + } + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + Mutex::Lock _l(_nets_m); + + auto input = req.matches[1]; + const uint64_t nwid = Utils::hexStrToU64(input.str().c_str()); + if (_nets.find(nwid) != _nets.end()) { + auto out = json::object(); + NetworkState &ns = _nets[nwid]; + _networkToJson(out, ns); + setContent(req, res, out.dump()); + return; + } + setContent(req, res, ""); + res.status = 404; + }); + + auto networkPost = [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto input = req.matches[1]; + uint64_t wantnw = Utils::hexStrToU64(input.str().c_str()); + _node->join(wantnw, (void*)0, (void*)0); + auto out = json::object(); + Mutex::Lock l(_nets_m); + if (!_nets.empty()) { + NetworkState &ns = _nets[wantnw]; + try { + json j(OSUtils::jsonParse(req.body)); + + json &allowManaged = j["allowManaged"]; + if (allowManaged.is_boolean()) { + ns.setAllowManaged((bool)allowManaged); } - json &settings = res["config"]["settings"]; - settings["allowTcpFallbackRelay"] = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],_allowTcpFallbackRelay); - settings["forceTcpRelay"] = OSUtils::jsonBool(settings["forceTcpRelay"],_forceTcpRelay); - settings["primaryPort"] = OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; - settings["secondaryPort"] = OSUtils::jsonInt(settings["secondaryPort"],(uint64_t)_secondaryPort) & 0xffff; - settings["tertiaryPort"] = OSUtils::jsonInt(settings["tertiaryPort"],(uint64_t)_tertiaryPort) & 0xffff; - // Enumerate all local address/port pairs that this node is listening on - std::vector boundAddrs(_binder.allBoundLocalInterfaceAddresses()); - auto boundAddrArray = json::array(); - for (int i = 0; i < boundAddrs.size(); i++) { - char ipBuf[64] = { 0 }; - boundAddrs[i].toString(ipBuf); - boundAddrArray.push_back(ipBuf); + json& allowGlobal = j["allowGlobal"]; + if (allowGlobal.is_boolean()) { + ns.setAllowGlobal((bool)allowGlobal); } - settings["listeningOn"] = boundAddrArray; - // Enumerate all external address/port pairs that are reported for this node - std::vector surfaceAddrs = _node->SurfaceAddresses(); - auto surfaceAddrArray = json::array(); - for (int i = 0; i < surfaceAddrs.size(); i++) { - char ipBuf[64] = { 0 }; - surfaceAddrs[i].toString(ipBuf); - surfaceAddrArray.push_back(ipBuf); + json& allowDefault = j["allowDefault"]; + if (allowDefault.is_boolean()) { + ns.setAllowDefault((bool)allowDefault); } - settings["surfaceAddresses"] = surfaceAddrArray; + json& allowDNS = j["allowDNS"]; + if (allowDNS.is_boolean()) { + ns.setAllowDNS((bool)allowDNS); + } + } catch (...) { + // discard invalid JSON + } + setNetworkSettings(wantnw, ns.settings()); + if (ns.tap()) { + syncManagedStuff(ns,true,true,true); + } + + _networkToJson(out, ns); + } + setContent(req, res, out.dump()); + }; + _controlPlane.Post("/network/([0-9a-fA-F]{16})", networkPost); + _controlPlane.Put("/network/([0-9a-fA-F]){16}", networkPost); + + _controlPlane.Delete("/network/([0-9a-fA-F]{16})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + auto input = req.matches[1]; + auto out = json::object(); + ZT_VirtualNetworkList *nws = _node->networks(); + uint64_t wantnw = Utils::hexStrToU64(input.str().c_str()); + for(unsigned long i=0; i < nws->networkCount; ++i) { + if (nws->networks[i].nwid == wantnw) { + _node->leave(wantnw, (void**)0, (void*)0); + out["result"] = true; + } + } + _node->freeQueryResult((void*)nws); + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/peer", [&, setContent](const httplib::Request &req, httplib::Response &res) { + ZT_PeerList *pl = _node->peers(); + auto out = nlohmann::json::array(); + + for(unsigned long i=0;ipeerCount;++i) { + nlohmann::json pj; + SharedPtr bond = SharedPtr(); + if (pl->peers[i].isBonded) { + const uint64_t id = pl->peers[i].address; + bond = _node->bondController()->getBondByPeerId(id); + } + _peerToJson(pj,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); + out.push_back(pj); + } + _node->freeQueryResult((void*)pl); + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/peer/([0-9a-fA-F]{10})", [&, setContent](const httplib::Request &req, httplib::Response &res) { + ZT_PeerList *pl = _node->peers(); + + auto input = req.matches[1]; + uint64_t wantp = Utils::hexStrToU64(input.str().c_str()); + auto out = json::object(); + for(unsigned long i=0;ipeerCount;++i) { + if (pl->peers[i].address == wantp) { + SharedPtr bond = SharedPtr(); + if (pl->peers[i].isBonded) { + bond = _node->bondController()->getBondByPeerId(wantp); + } + _peerToJson(out,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); + break; + } + } + _node->freeQueryResult((void*)pl); + setContent(req, res, out.dump()); + }); + + _controlPlane.Get("/status", [&, setContent](const httplib::Request &req, httplib::Response &res) { + ZT_NodeStatus status; + _node->status(&status); + + auto out = json::object(); + char tmp[256] = {}; + + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",status.address); + out["address"] = tmp; + out["publicIdentity"] = status.publicIdentity; + out["online"] = (bool)(status.online != 0); + out["tcpFallbackActive"] = (_tcpFallbackTunnel != (TcpConnection *)0); + out["versionMajor"] = ZEROTIER_ONE_VERSION_MAJOR; + out["versionMinor"] = ZEROTIER_ONE_VERSION_MINOR; + out["versionRev"] = ZEROTIER_ONE_VERSION_REVISION; + out["versionBuild"] = ZEROTIER_ONE_VERSION_BUILD; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + out["version"] = tmp; + out["clock"] = OSUtils::now(); + + { + Mutex::Lock _l(_localConfig_m); + out["config"] = _localConfig; + } + json &settings = out["config"]["settings"]; + settings["allowTcpFallbackRelay"] = OSUtils::jsonBool(settings["allowTcpFallbackRelay"],_allowTcpFallbackRelay); + settings["forceTcpRelay"] = OSUtils::jsonBool(settings["forceTcpRelay"],_forceTcpRelay); + settings["primaryPort"] = OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; + settings["secondaryPort"] = OSUtils::jsonInt(settings["secondaryPort"],(uint64_t)_secondaryPort) & 0xffff; + settings["tertiaryPort"] = OSUtils::jsonInt(settings["tertiaryPort"],(uint64_t)_tertiaryPort) & 0xffff; + // Enumerate all local address/port pairs that this node is listening on + std::vector boundAddrs(_binder.allBoundLocalInterfaceAddresses()); + auto boundAddrArray = json::array(); + for (int i = 0; i < boundAddrs.size(); i++) { + char ipBuf[64] = { 0 }; + boundAddrs[i].toString(ipBuf); + boundAddrArray.push_back(ipBuf); + } + settings["listeningOn"] = boundAddrArray; + // Enumerate all external address/port pairs that are reported for this node + std::vector surfaceAddrs = _node->SurfaceAddresses(); + auto surfaceAddrArray = json::array(); + for (int i = 0; i < surfaceAddrs.size(); i++) { + char ipBuf[64] = { 0 }; + surfaceAddrs[i].toString(ipBuf); + surfaceAddrArray.push_back(ipBuf); + } + settings["surfaceAddresses"] = surfaceAddrArray; #ifdef ZT_USE_MINIUPNPC - settings["portMappingEnabled"] = OSUtils::jsonBool(settings["portMappingEnabled"],true); + settings["portMappingEnabled"] = OSUtils::jsonBool(settings["portMappingEnabled"],true); #else - settings["portMappingEnabled"] = false; // not supported in build + settings["portMappingEnabled"] = false; // not supported in build #endif #ifndef ZT_SDK - settings["softwareUpdate"] = OSUtils::jsonString(settings["softwareUpdate"],ZT_SOFTWARE_UPDATE_DEFAULT); - settings["softwareUpdateChannel"] = OSUtils::jsonString(settings["softwareUpdateChannel"],ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL); + settings["softwareUpdate"] = OSUtils::jsonString(settings["softwareUpdate"],ZT_SOFTWARE_UPDATE_DEFAULT); + settings["softwareUpdateChannel"] = OSUtils::jsonString(settings["softwareUpdateChannel"],ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL); #endif - const World planet(_node->planet()); - res["planetWorldId"] = planet.id(); - res["planetWorldTimestamp"] = planet.timestamp(); + const World planet(_node->planet()); + out["planetWorldId"] = planet.id(); + out["planetWorldTimestamp"] = planet.timestamp(); - scode = 200; - } else if (ps[0] == "moon") { - std::vector moons(_node->moons()); - if (ps.size() == 1) { - // Return [array] of all moons + setContent(req, res, out.dump()); + }); - res = json::array(); - for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { - json mj; - _moonToJson(mj,*m); - res.push_back(mj); - } - - scode = 200; - } else { - // Return a single moon by ID - - const uint64_t id = Utils::hexStrToU64(ps[1].c_str()); - for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { - if (m->id() == id) { - _moonToJson(res,*m); - scode = 200; - break; - } - } - - } - } else if (ps[0] == "network") { - Mutex::Lock _l(_nets_m); - if (ps.size() == 1) { - // Return [array] of all networks - - res = nlohmann::json::array(); - - for (auto it = _nets.begin(); it != _nets.end(); ++it) { - NetworkState &ns = it->second; - nlohmann::json nj; - _networkToJson(nj, ns); - res.push_back(nj); - } - - scode = 200; - } else if (ps.size() == 2) { - // Return a single network by ID or 404 if not found - - const uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); - if (_nets.find(wantnw) != _nets.end()) { - res = json::object(); - NetworkState& ns = _nets[wantnw]; - _networkToJson(res, ns); - scode = 200; - } - } else { - scode = 404; - } - } else if (ps[0] == "peer") { - ZT_PeerList *pl = _node->peers(); - if (pl) { - if (ps.size() == 1) { - // Return [array] of all peers - - res = nlohmann::json::array(); - for(unsigned long i=0;ipeerCount;++i) { - nlohmann::json pj; - SharedPtr bond = SharedPtr(); - if (pl->peers[i].isBonded) { - const uint64_t id = pl->peers[i].address; - bond = _node->bondController()->getBondByPeerId(id); - } - _peerToJson(pj,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); - res.push_back(pj); - } - - scode = 200; - } else if (ps.size() == 2) { - // Return a single peer by ID or 404 if not found - - uint64_t wantp = Utils::hexStrToU64(ps[1].c_str()); - for(unsigned long i=0;ipeerCount;++i) { - if (pl->peers[i].address == wantp) { - SharedPtr bond = SharedPtr(); - if (pl->peers[i].isBonded) { - bond = _node->bondController()->getBondByPeerId(wantp); - } - _peerToJson(res,&(pl->peers[i]),bond,(_tcpFallbackTunnel != (TcpConnection *)0)); - scode = 200; - break; - } - } - - } else scode = 404; - _node->freeQueryResult((void *)pl); - } else scode = 500;\ - } else if (ps[0] == "metrics") { - std::string statspath = _homePath + ZT_PATH_SEPARATOR + "metrics.prom"; - if (!OSUtils::readFile(statspath.c_str(), responseBody)) { - scode = 500; - } else { - scode = 200; - responseContentType = "text/plain"; - } - } else { - if (_controller) { - scode = _controller->handleControlPlaneHttpGET(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - } else scode = 404; - } #if ZT_SSO_ENABLED - } else if (ps[0] == "sso") { - std::string htmlTemplatePath = _homePath + ZT_PATH_SEPARATOR + "sso-auth.template.html"; - std::string htmlTemplate; - if (!OSUtils::readFile(htmlTemplatePath.c_str(), htmlTemplate)) { - htmlTemplate = ssoResponseTemplate; - } + _controlPlane.Get("/sso", [this](const httplib::Request &req, httplib::Response &res) { + std::string htmlTemplatePath = _homePath + ZT_PATH_SEPARATOR + "sso-auth.template.html"; + std::string htmlTemplate; + if (!OSUtils::readFile(htmlTemplatePath.c_str(), htmlTemplate)) { + htmlTemplate = ssoResponseTemplate; + } - responseContentType = "text/html"; - json outData; + std::string responseContentType = "text/html"; + std::string responseBody = ""; + json outData; - char *error = zeroidc::zeroidc_get_url_param_value("error", path.c_str()); - if (error != nullptr) { - char *desc = zeroidc::zeroidc_get_url_param_value("error_description", path.c_str()); - scode = 500; - json data; - outData["isError"] = true; - outData["messageText"] = (std::string("ERROR ") + error + std::string(": ") + desc); - responseBody = inja::render(htmlTemplate, outData); + if (req.has_param("error")) { + std::string error = req.get_param_value("error"); + std::string desc = req.get_param_value("error_description"); - zeroidc::free_cstr(desc); - zeroidc::free_cstr(error); + json data; + outData["isError"] = true; + outData["messageText"] = (std::string("ERROR ") + error + std::string(": ") + desc); + responseBody = inja::render(htmlTemplate, outData); - return scode; - } + res.set_content(responseBody, responseContentType); + res.status = 500; + return; + } - // SSO redirect handling - char* state = zeroidc::zeroidc_get_url_param_value("state", path.c_str()); - char* nwid = zeroidc::zeroidc_network_id_from_state(state); + // SSO redirect handling + std::string state = req.get_param_value("state"); + char* nwid = zeroidc::zeroidc_network_id_from_state(state.c_str()); - outData["networkId"] = std::string(nwid); + outData["networkId"] = std::string(nwid); - const uint64_t id = Utils::hexStrToU64(nwid); - - zeroidc::free_cstr(nwid); - zeroidc::free_cstr(state); + const uint64_t id = Utils::hexStrToU64(nwid); - Mutex::Lock l(_nets_m); - if (_nets.find(id) != _nets.end()) { - NetworkState& ns = _nets[id]; - char* code = zeroidc::zeroidc_get_url_param_value("code", path.c_str()); - char *ret = ns.doTokenExchange(code); - json ssoResult = json::parse(ret); - if (ssoResult.is_object()) { - if (ssoResult.contains("errorMessage")) { - outData["isError"] = true; - outData["messageText"] = ssoResult["errorMessage"]; - responseBody = inja::render(htmlTemplate, outData); - scode = 500; - } else { - scode = 200; - outData["isError"] = false; - outData["messageText"] = "Authentication Successful. You may now access the network."; - responseBody = inja::render(htmlTemplate, outData); - } - } else { - // not an object? We got a problem - outData["isError"] = true; - outData["messageText"] = "ERROR: Unkown SSO response. Please contact your administrator."; - responseBody = inja::render(htmlTemplate, outData); - scode= 500; - } + zeroidc::free_cstr(nwid); - zeroidc::free_cstr(code); - zeroidc::free_cstr(ret); + Mutex::Lock l(_nets_m); + if (_nets.find(id) != _nets.end()) { + NetworkState& ns = _nets[id]; + std::string code = req.get_param_value("code"); + char *ret = ns.doTokenExchange(code.c_str()); + json ssoResult = json::parse(ret); + if (ssoResult.is_object()) { + if (ssoResult.contains("errorMessage")) { + outData["isError"] = true; + outData["messageText"] = ssoResult["errorMessage"]; + responseBody = inja::render(htmlTemplate, outData); + res.set_content(responseBody, responseContentType); + res.status = 500; + } else { + outData["isError"] = false; + outData["messageText"] = "Authentication Successful. You may now access the network."; + responseBody = inja::render(htmlTemplate, outData); + res.set_content(responseBody, responseContentType); + } + } else { + // not an object? We got a problem + outData["isError"] = true; + outData["messageText"] = "ERROR: Unkown SSO response. Please contact your administrator."; + responseBody = inja::render(htmlTemplate, outData); + res.set_content(responseBody, responseContentType); + res.status = 500; + } - return scode; - } else { - scode = 404; - } + zeroidc::free_cstr(ret); + } + }); #endif - } else { - scode = 401; // isAuth == false && !sso + + _controlPlane.Get("/metrics", [this](const httplib::Request &req, httplib::Response &res) { + std::string statspath = _homePath + ZT_PATH_SEPARATOR + "metrics.prom"; + std::string metrics; + if (OSUtils::readFile(statspath.c_str(), metrics)) { + res.set_content(metrics, "text/plain"); + } else { + res.set_content("{}", "application/json"); + res.status = 500; + } + }); + + _controlPlane.set_exception_handler([&, setContent](const httplib::Request &req, httplib::Response &res, std::exception_ptr ep) { + char buf[1024]; + auto fmt = "{\"error\": %d, \"description\": \"%s\"}"; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, 500, e.what()); + } catch (...) { + snprintf(buf, sizeof(buf), fmt, 500, "Unknown Exception"); } - } else if ((httpMethod == HTTP_POST)||(httpMethod == HTTP_PUT)) { - if (isAuth) { - if (ps[0] == "bond") { - if (_node->bondController()->inUse()) { - if (ps.size() == 3) { - if (ps[2].length() == 10) { - // check if hex string - const uint64_t id = Utils::hexStrToU64(ps[2].c_str()); - if (ps[1] == "rotate") { - exit(0); - SharedPtr bond = _node->bondController()->getBondByPeerId(id); - if (bond) { - scode = bond->abForciblyRotateLink() ? 200 : 400; - } else { - fprintf(stderr, "unable to find bond to peer %llx\n", (unsigned long long)id); - scode = 400; - } - } - } - } - } else { - scode = 400; /* bond controller is not enabled */ - } - } else if (ps[0] == "config") { - // Right now we only support writing the things the UI supports changing. - if (ps.size() == 2) { - if (ps[1] == "settings") { - try { - json j(OSUtils::jsonParse(body)); - if (j.is_object()) { - Mutex::Lock lcl(_localConfig_m); - json lc(_localConfig); - for(json::const_iterator s(j.begin());s!=j.end();++s) { - lc["settings"][s.key()] = s.value(); - } - std::string lcStr = OSUtils::jsonDump(lc, 4); - if (OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S "local.conf").c_str(), lcStr)) { - _localConfig = lc; - } - } else { - scode = 400; - } - } catch ( ... ) { - scode = 400; - } - } else { - scode = 404; - } - } else { - scode = 404; - } - } else if (ps[0] == "moon") { - if (ps.size() == 2) { + setContent(req, res, buf); + res.status = 500; + }); - uint64_t seed = 0; - try { - json j(OSUtils::jsonParse(body)); - if (j.is_object()) { - seed = Utils::hexStrToU64(OSUtils::jsonString(j["seed"],"0").c_str()); - } - } catch ( ... ) { - // discard invalid JSON - } + if (_controller) { + _controller->configureHTTPControlPlane(_controlPlane, setContent); + } - std::vector moons(_node->moons()); - const uint64_t id = Utils::hexStrToU64(ps[1].c_str()); - for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { - if (m->id() == id) { - _moonToJson(res,*m); - scode = 200; - break; - } - } + _controlPlane.set_pre_routing_handler(authCheck); - if ((scode != 200)&&(seed != 0)) { - char tmp[64]; - OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",id); - res["id"] = tmp; - res["roots"] = json::array(); - res["timestamp"] = 0; - res["signature"] = json(); - res["updatesMustBeSignedBy"] = json(); - res["waiting"] = true; - _node->orbit((void *)0,id,seed); - scode = 200; - } +#if ZT_DEBUG==1 + _controlPlane.set_logger([](const httplib::Request &req, const httplib::Response &res) { + fprintf(stderr, "%s", http_log(req, res).c_str()); + }); +#endif + if (_primaryPort==0) { + fprintf(stderr, "unable to determine local control port"); + exit(-1); + } - } else scode = 404; - } else if (ps[0] == "network") { - if (ps.size() == 2) { + if(!_controlPlane.bind_to_port("0.0.0.0", _primaryPort)) { + fprintf(stderr, "Error binding control plane to port %d\n", _primaryPort); + exit(-1); + } - uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); - _node->join(wantnw,(void *)0,(void *)0); // does nothing if we are a member - Mutex::Lock l(_nets_m); - if (!_nets.empty()) { - if (_nets.find(wantnw) != _nets.end()) { - NetworkState& ns = _nets[wantnw]; - try { - json j(OSUtils::jsonParse(body)); - - json &allowManaged = j["allowManaged"]; - if (allowManaged.is_boolean()) { - ns.setAllowManaged((bool)allowManaged); - } - json& allowGlobal = j["allowGlobal"]; - if (allowGlobal.is_boolean()) { - ns.setAllowGlobal((bool)allowGlobal); - } - json& allowDefault = j["allowDefault"]; - if (allowDefault.is_boolean()) { - ns.setAllowDefault((bool)allowDefault); - } - json& allowDNS = j["allowDNS"]; - if (allowDNS.is_boolean()) { - ns.setAllowDNS((bool)allowDNS); - } - } catch (...) { - // discard invalid JSON - } - setNetworkSettings(wantnw, ns.settings()); - if (ns.tap()) { - syncManagedStuff(ns,true,true,true); - } - - _networkToJson(res, ns); - - scode = 200; - } - } else scode = 500; - - } else scode = 404; - } else { - if (_controller) - scode = _controller->handleControlPlaneHttpPOST(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - else scode = 404; - } - } else { - scode = 401; // isAuth == false + _serverThread = std::thread([&] { + fprintf(stderr, "Starting Control Plane...\n"); + if(!_controlPlane.listen_after_bind()) { + fprintf(stderr, "Error on listen_after_bind()\n"); } - } else if (httpMethod == HTTP_DELETE) { - if (isAuth) { + fprintf(stderr, "Control Plane Stopped\n"); + }); - if (ps[0] == "moon") { - if (ps.size() == 2) { - _node->deorbit((void *)0,Utils::hexStrToU64(ps[1].c_str())); - res["result"] = true; - scode = 200; - } // else 404 - } else if (ps[0] == "network") { - ZT_VirtualNetworkList *nws = _node->networks(); - if (nws) { - if (ps.size() == 2) { - uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); - for(unsigned long i=0;inetworkCount;++i) { - if (nws->networks[i].nwid == wantnw) { - _node->leave(wantnw,(void **)0,(void *)0); - res["result"] = true; - scode = 200; - break; - } - } - } // else 404 - _node->freeQueryResult((void *)nws); - } else scode = 500; - } else { - if (_controller) - scode = _controller->handleControlPlaneHttpDELETE(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); - else scode = 404; - } - } else scode = 401; // isAuth = false - } else { - scode = 400; - } - - if (responseBody.length() == 0) { - if ((res.is_object())||(res.is_array())) - responseBody = OSUtils::jsonDump(res); - else responseBody = "{}"; - responseContentType = "application/json"; - } - - // Wrap result in jsonp function call if the user included a jsonp= url argument. - // Also double-check isAuth since forbidding this without auth feels safer. - std::map::const_iterator jsonp(urlArgs.find("jsonp")); - if ((isAuth)&&(jsonp != urlArgs.end())&&(responseContentType == "application/json")) { - if (responseBody.length() > 0) - responseBody = jsonp->second + "(" + responseBody + ");"; - else responseBody = jsonp->second + "(null);"; - responseContentType = "application/javascript"; - } - - return scode; - } + } // Must be called after _localConfig is read or modified void applyLocalConfig() @@ -2566,44 +2615,45 @@ public: tc->lastReceive = OSUtils::now(); switch(tc->type) { - case TcpConnection::TCP_UNCATEGORIZED_INCOMING: - switch(reinterpret_cast(data)[0]) { - // HTTP: GET, PUT, POST, HEAD, DELETE - case 'G': - case 'P': - case 'D': - case 'H': { - // This is only allowed from IPs permitted to access the management - // backplane, which is just 127.0.0.1/::1 unless otherwise configured. - bool allow; - { - Mutex::Lock _l(_localConfig_m); - if (_allowManagementFrom.empty()) { - allow = (tc->remoteAddr.ipScope() == InetAddress::IP_SCOPE_LOOPBACK); - } else { - allow = false; - for(std::vector::const_iterator i(_allowManagementFrom.begin());i!=_allowManagementFrom.end();++i) { - if (i->containsAddress(tc->remoteAddr)) { - allow = true; - break; - } - } - } - } - if (allow) { - tc->type = TcpConnection::TCP_HTTP_INCOMING; - phyOnTcpData(sock,uptr,data,len); - } else { - _phy.close(sock); - } - } break; + // TODO: Remove Me + // case TcpConnection::TCP_UNCATEGORIZED_INCOMING: + // switch(reinterpret_cast(data)[0]) { + // // HTTP: GET, PUT, POST, HEAD, DELETE + // case 'G': + // case 'P': + // case 'D': + // case 'H': { + // // This is only allowed from IPs permitted to access the management + // // backplane, which is just 127.0.0.1/::1 unless otherwise configured. + // bool allow; + // { + // Mutex::Lock _l(_localConfig_m); + // if (_allowManagementFrom.empty()) { + // allow = (tc->remoteAddr.ipScope() == InetAddress::IP_SCOPE_LOOPBACK); + // } else { + // allow = false; + // for(std::vector::const_iterator i(_allowManagementFrom.begin());i!=_allowManagementFrom.end();++i) { + // if (i->containsAddress(tc->remoteAddr)) { + // allow = true; + // break; + // } + // } + // } + // } + // if (allow) { + // tc->type = TcpConnection::TCP_HTTP_INCOMING; + // phyOnTcpData(sock,uptr,data,len); + // } else { + // _phy.close(sock); + // } + // } break; - // Drop unknown protocols - default: - _phy.close(sock); - break; - } - return; + // // Drop unknown protocols + // default: + // _phy.close(sock); + // break; + // } + // return; case TcpConnection::TCP_HTTP_INCOMING: case TcpConnection::TCP_HTTP_OUTGOING: @@ -3335,56 +3385,6 @@ public: _node->processVirtualNetworkFrame((void*)0, OSUtils::now(), nwid, from.toInt(), to.toInt(), etherType, vlanId, data, len, &_nextBackgroundTaskDeadline); } - inline void onHttpRequestToServer(TcpConnection* tc) - { - char tmpn[4096]; - std::string data; - std::string contentType("text/plain"); // default if not changed in handleRequest() - unsigned int scode = 404; - - // Note that we check allowed IP ranges when HTTP connections are first detected in - // phyOnTcpData(). If we made it here the source IP is okay. - - try { - scode = handleControlPlaneHttpRequest(tc->remoteAddr, tc->parser.method, tc->url, tc->headers, tc->readq, data, contentType); - } - catch (std::exception& exc) { - fprintf(stderr, "WARNING: unexpected exception processing control HTTP request: %s" ZT_EOL_S, exc.what()); - scode = 500; - } - catch (...) { - fprintf(stderr, "WARNING: unexpected exception processing control HTTP request: unknown exception" ZT_EOL_S); - scode = 500; - } - - const char* scodestr; - switch (scode) { - case 200: scodestr = "OK"; break; - case 400: scodestr = "Bad Request"; break; - case 401: scodestr = "Unauthorized"; break; - case 403: scodestr = "Forbidden"; break; - case 404: scodestr = "Not Found"; break; - case 500: scodestr = "Internal Server Error"; break; - case 501: scodestr = "Not Implemented"; break; - case 503: scodestr = "Service Unavailable"; break; - default: scodestr = "Error"; break; - } - - OSUtils::ztsnprintf(tmpn, sizeof(tmpn), "HTTP/1.1 %.3u %s\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nContent-Type: %s\r\nContent-Length: %lu\r\nConnection: close\r\n\r\n", - scode, - scodestr, - contentType.c_str(), - (unsigned long)data.length()); - { - Mutex::Lock _l(tc->writeq_m); - tc->writeq = tmpn; - if (tc->parser.method != HTTP_HEAD) - tc->writeq.append(data); - } - - _phy.setNotifyWritable(tc->sock, true); - } - inline void onHttpResponseFromClient(TcpConnection* tc) { _phy.close(tc->sock); @@ -3599,7 +3599,6 @@ static int ShttpOnMessageComplete(http_parser *parser) { TcpConnection *tc = reinterpret_cast(parser->data); if (tc->type == TcpConnection::TCP_HTTP_INCOMING) { - tc->parent->onHttpRequestToServer(tc); } else { tc->parent->onHttpResponseFromClient(tc); } diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index 3722b03ad..aca2d49af 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -417,7 +417,7 @@ $(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories) ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions) 4996 - false + true stdcpp17 true stdc11 @@ -439,7 +439,7 @@ $(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories) ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions) 4996 - false + true stdcpp17 stdc11 true @@ -461,11 +461,11 @@ ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_RULES_ENGINE_DEBUGGING;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions) true 4996 - false stdcpp17 stdc11 false false + true true @@ -507,11 +507,11 @@ ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions) true 4996 - false stdcpp17 stdc11 false false + true true @@ -558,7 +558,7 @@ true 4996 Guard - false + true stdcpp17 None false @@ -597,7 +597,6 @@ Guard false Cdecl - false stdcpp17 None false @@ -606,6 +605,7 @@ stdc11 false false + true false diff --git a/zeroidc/Cargo.lock b/zeroidc/Cargo.lock index 904766cf1..1910bdad5 100644 --- a/zeroidc/Cargo.lock +++ b/zeroidc/Cargo.lock @@ -834,9 +834,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.48" +version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ "bitflags", "cfg-if", @@ -866,11 +866,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.83" +version = "0.9.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", diff --git a/zeroidc/src/ext.rs b/zeroidc/src/ext.rs index d87724a78..fd1e2e96b 100644 --- a/zeroidc/src/ext.rs +++ b/zeroidc/src/ext.rs @@ -89,7 +89,7 @@ pub extern "C" fn zeroidc_delete(ptr: *mut ZeroIDC) { idc.stop(); unsafe { - Box::from_raw(ptr); + let _ = Box::from_raw(ptr); } } diff --git a/zeroidc/src/lib.rs b/zeroidc/src/lib.rs index 21319481f..0c63cb946 100644 --- a/zeroidc/src/lib.rs +++ b/zeroidc/src/lib.rs @@ -35,6 +35,7 @@ use std::str::from_utf8; use std::sync::{Arc, Mutex}; use std::thread::{sleep, spawn, JoinHandle}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +#[cfg(debug_assertions)] use time::{format_description, OffsetDateTime}; use url::Url; @@ -59,7 +60,6 @@ pub struct ZeroIDC { ))] struct Inner { running: bool, - issuer: String, auth_endpoint: String, provider: String, oidc_thread: Option>, @@ -123,7 +123,6 @@ impl ZeroIDC { let idc = ZeroIDC { inner: Arc::new(Mutex::new(Inner { running: false, - issuer: issuer.to_string(), provider: provider.to_string(), auth_endpoint: auth_ep.to_string(), oidc_thread: None,