Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include <boost/asio/ip/address.hpp>
20 : : #include <cstdint>
21 : : #include <optional>
22 : : #include <string>
23 : :
24 : : #include "wirestead/base/constants.hpp"
25 : : #include "wirestead/util/input_validator.hpp"
26 : :
27 : : namespace wirestead {
28 : : namespace config {
29 : :
30 : : // True for an address literal inside the multicast ranges - 224.0.0.0/4 for
31 : : // IPv4, ff00::/8 for IPv6. Anything else, including a hostname or a unicast
32 : : // address, is rejected: joining a group on a unicast address fails at the
33 : : // setsockopt with an error that says nothing about which setting was wrong.
34 : 8 : inline bool is_multicast_address(const std::string& address) {
35 : 8 : boost::system::error_code ec;
36 : 8 : const auto parsed = boost::asio::ip::make_address(address, ec);
37 [ + + ]: 8 : if (ec) return false;
38 : 7 : return parsed.is_multicast();
39 : : }
40 : :
41 : : struct UdpConfig {
42 : : std::string bind_address = "0.0.0.0";
43 : : uint16_t local_port = 0;
44 : : std::optional<std::string> remote_address;
45 : : std::optional<uint16_t> remote_port;
46 : : size_t backpressure_threshold = base::constants::DEFAULT_BACKPRESSURE_THRESHOLD;
47 : : base::constants::BackpressureStrategy backpressure_strategy = base::constants::BackpressureStrategy::Reliable;
48 : : bool enable_broadcast = false;
49 : : bool reuse_address = false;
50 : :
51 : : // Join this multicast group after binding, so the socket receives datagrams
52 : : // addressed to it. IPv4 groups are 224.0.0.0/4, IPv6 groups are ff00::/8.
53 : : //
54 : : // `multicast_interface` picks which NIC to join on, by its local IPv4
55 : : // address. A robot with a sensor on one interface and a network on another
56 : : // needs it; leave it empty to let the kernel choose, which is only reliable
57 : : // with a single interface. IPv6 always uses the kernel's choice.
58 : : //
59 : : // Receiving is all this covers. Sending to a group already works through
60 : : // remote_address, at the default TTL of 1 - one hop, so the local subnet
61 : : // only, which is where a robot's sensors are.
62 : : std::optional<std::string> multicast_group;
63 : : std::optional<std::string> multicast_interface;
64 : : bool enable_memory_pool = true;
65 : : bool stop_on_callback_exception = false;
66 : : size_t send_buffer_size = 0;
67 : : size_t receive_buffer_size = 0;
68 : :
69 : 90 : bool is_valid() const {
70 : : // bind_address/remote_address are passed straight to
71 : : // boost::asio::ip::make_address() (wirestead/transport/udp/udp.cc) -
72 : : // literal IPv4/IPv6 addresses only, no hostname resolution.
73 [ + + + + : 90 : if (!util::InputValidator::is_valid_ipv4(bind_address) && !util::InputValidator::is_valid_ipv6(bind_address)) {
+ + ]
74 : 1 : return false;
75 : : }
76 [ + - ]: 89 : if (backpressure_threshold < base::constants::MIN_BACKPRESSURE_THRESHOLD ||
77 [ - + ]: 89 : backpressure_threshold > base::constants::MAX_BACKPRESSURE_THRESHOLD) {
78 : 0 : return false;
79 : : }
80 [ - + ]: 89 : if (remote_address.has_value() != remote_port.has_value()) return false;
81 [ + + + + : 90 : if (remote_address && !util::InputValidator::is_valid_ipv4(*remote_address) &&
+ + ]
82 [ + - ]: 1 : !util::InputValidator::is_valid_ipv6(*remote_address)) {
83 : 1 : return false;
84 : : }
85 [ + + - + : 88 : if (remote_port && *remote_port == 0) return false;
- + ]
86 [ + + + + : 88 : if (multicast_group && !is_multicast_address(*multicast_group)) return false;
+ + ]
87 [ + + + + : 85 : if (multicast_interface && !util::InputValidator::is_valid_ipv4(*multicast_interface)) return false;
+ + ]
88 [ + + + + ]: 84 : if (send_buffer_size != 0 && (send_buffer_size < base::constants::MIN_SOCKET_BUFFER_SIZE ||
89 [ - + ]: 1 : send_buffer_size > base::constants::MAX_SOCKET_BUFFER_SIZE)) {
90 : 1 : return false;
91 : : }
92 [ + + + - ]: 83 : if (receive_buffer_size != 0 && (receive_buffer_size < base::constants::MIN_SOCKET_BUFFER_SIZE ||
93 [ - + ]: 1 : receive_buffer_size > base::constants::MAX_SOCKET_BUFFER_SIZE)) {
94 : 0 : return false;
95 : : }
96 : 83 : return true;
97 : : }
98 : :
99 : 78 : void validate_and_clamp() {
100 [ + + ]: 78 : if (backpressure_threshold < base::constants::MIN_BACKPRESSURE_THRESHOLD) {
101 : 1 : backpressure_threshold = base::constants::MIN_BACKPRESSURE_THRESHOLD;
102 [ - + ]: 77 : } else if (backpressure_threshold > base::constants::MAX_BACKPRESSURE_THRESHOLD) {
103 : 0 : backpressure_threshold = base::constants::MAX_BACKPRESSURE_THRESHOLD;
104 : : }
105 : :
106 [ + + + - ]: 78 : if (send_buffer_size != 0 && send_buffer_size < base::constants::MIN_SOCKET_BUFFER_SIZE) {
107 : 1 : send_buffer_size = base::constants::MIN_SOCKET_BUFFER_SIZE;
108 [ - + ]: 77 : } else if (send_buffer_size > base::constants::MAX_SOCKET_BUFFER_SIZE) {
109 : 0 : send_buffer_size = base::constants::MAX_SOCKET_BUFFER_SIZE;
110 : : }
111 : :
112 [ + + - + ]: 78 : if (receive_buffer_size != 0 && receive_buffer_size < base::constants::MIN_SOCKET_BUFFER_SIZE) {
113 : 0 : receive_buffer_size = base::constants::MIN_SOCKET_BUFFER_SIZE;
114 [ + + ]: 78 : } else if (receive_buffer_size > base::constants::MAX_SOCKET_BUFFER_SIZE) {
115 : 1 : receive_buffer_size = base::constants::MAX_SOCKET_BUFFER_SIZE;
116 : : }
117 : 78 : }
118 : : };
119 : :
120 : : } // namespace config
121 : : } // namespace wirestead
|