98.86% Lines (173/175) 100.00% Functions (67/67)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
9   // 9   //
10   10  
11   /** @file native_socket_option.hpp 11   /** @file native_socket_option.hpp
12   12  
13   Inline socket option types using platform-specific constants. 13   Inline socket option types using platform-specific constants.
14   All methods are `constexpr` or trivially inlined, giving zero 14   All methods are `constexpr` or trivially inlined, giving zero
15   overhead compared to hand-written `setsockopt` calls. 15   overhead compared to hand-written `setsockopt` calls.
16   16  
17   This header includes platform socket headers 17   This header includes platform socket headers
18   (`<sys/socket.h>`, `<netinet/tcp.h>`, etc.). 18   (`<sys/socket.h>`, `<netinet/tcp.h>`, etc.).
19   For a version that avoids platform includes, use 19   For a version that avoids platform includes, use
20   `<boost/corosio/socket_option.hpp>` 20   `<boost/corosio/socket_option.hpp>`
21   (`boost::corosio::socket_option`). 21   (`boost::corosio::socket_option`).
22   22  
23   Both variants satisfy the same option-type interface and work 23   Both variants satisfy the same option-type interface and work
24   interchangeably with `tcp_socket::set_option` / 24   interchangeably with `tcp_socket::set_option` /
25   `tcp_socket::get_option` and the corresponding acceptor methods. 25   `tcp_socket::get_option` and the corresponding acceptor methods.
26   26  
27   @see boost::corosio::socket_option 27   @see boost::corosio::socket_option
28   */ 28   */
29   29  
30   #ifndef BOOST_COROSIO_NATIVE_NATIVE_SOCKET_OPTION_HPP 30   #ifndef BOOST_COROSIO_NATIVE_NATIVE_SOCKET_OPTION_HPP
31   #define BOOST_COROSIO_NATIVE_NATIVE_SOCKET_OPTION_HPP 31   #define BOOST_COROSIO_NATIVE_NATIVE_SOCKET_OPTION_HPP
32   32  
33   #ifdef _WIN32 33   #ifdef _WIN32
34   #include <winsock2.h> 34   #include <winsock2.h>
35   #include <ws2tcpip.h> 35   #include <ws2tcpip.h>
36   #else 36   #else
37   #include <netinet/in.h> 37   #include <netinet/in.h>
38   #include <netinet/tcp.h> 38   #include <netinet/tcp.h>
39   #include <sys/socket.h> 39   #include <sys/socket.h>
40   #endif 40   #endif
41   41  
42   // Some older systems define only the legacy names 42   // Some older systems define only the legacy names
43   #ifndef IPV6_JOIN_GROUP 43   #ifndef IPV6_JOIN_GROUP
44   #define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP 44   #define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP
45   #endif 45   #endif
46   #ifndef IPV6_LEAVE_GROUP 46   #ifndef IPV6_LEAVE_GROUP
47   #define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP 47   #define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP
48   #endif 48   #endif
49   49  
  50 + #include <boost/corosio/detail/except.hpp>
  51 + #include <boost/corosio/family.hpp>
  52 + #include <boost/corosio/ip_address.hpp>
50   #include <boost/corosio/ipv4_address.hpp> 53   #include <boost/corosio/ipv4_address.hpp>
51   #include <boost/corosio/ipv6_address.hpp> 54   #include <boost/corosio/ipv6_address.hpp>
52   55  
53   #include <cstddef> 56   #include <cstddef>
54   #include <cstring> 57   #include <cstring>
55   58  
56   namespace boost::corosio::native_socket_option { 59   namespace boost::corosio::native_socket_option {
57   60  
58   /** A socket option with a boolean value. 61   /** A socket option with a boolean value.
59   62  
60   Models socket options whose underlying representation is an `int` 63   Models socket options whose underlying representation is an `int`
61   where 0 means disabled and non-zero means enabled. The option's 64   where 0 means disabled and non-zero means enabled. The option's
62   protocol level and name are encoded as template parameters. 65   protocol level and name are encoded as template parameters.
63   66  
64   This is the native (inline) variant that includes platform 67   This is the native (inline) variant that includes platform
65   headers. For a type-erased version that avoids platform 68   headers. For a type-erased version that avoids platform
66   includes, use `boost::corosio::socket_option` instead. 69   includes, use `boost::corosio::socket_option` instead.
67   70  
68   @par Example 71   @par Example
69   @par !example boolean 72   @par !example boolean
70   73  
71   @tparam Level The protocol level (e.g. `SOL_SOCKET`, `IPPROTO_TCP`). 74   @tparam Level The protocol level (e.g. `SOL_SOCKET`, `IPPROTO_TCP`).
72   @tparam Name The option name (e.g. `TCP_NODELAY`, `SO_KEEPALIVE`). 75   @tparam Name The option name (e.g. `TCP_NODELAY`, `SO_KEEPALIVE`).
73   */ 76   */
74   template<int Level, int Name> 77   template<int Level, int Name>
75   class boolean 78   class boolean
76   { 79   {
77   int value_ = 0; 80   int value_ = 0;
78   81  
79   public: 82   public:
80   /// Construct with default value (disabled). 83   /// Construct with default value (disabled).
HITGIC 81   boolean() = default; 84   1494 boolean() = default;
82   85  
83   /** Construct with an explicit value. 86   /** Construct with an explicit value.
84   87  
85   @param v `true` to enable the option, `false` to disable. 88   @param v `true` to enable the option, `false` to disable.
86   */ 89   */
HITCBC 87   25 explicit boolean(bool v) noexcept : value_(v ? 1 : 0) {} 90   23 explicit boolean(bool v) noexcept : value_(v ? 1 : 0) {}
88   91  
89   /// Assign a new value. 92   /// Assign a new value.
90   boolean& operator=(bool v) noexcept 93   boolean& operator=(bool v) noexcept
91   { 94   {
92   value_ = v ? 1 : 0; 95   value_ = v ? 1 : 0;
93   return *this; 96   return *this;
94   } 97   }
95   98  
96   /// Return the option value. 99   /// Return the option value.
HITCBC 97   11 bool value() const noexcept 100   11 bool value() const noexcept
98   { 101   {
HITCBC 99   11 return value_ != 0; 102   11 return value_ != 0;
100   } 103   }
101   104  
102   /// Return the option value. 105   /// Return the option value.
103   explicit operator bool() const noexcept 106   explicit operator bool() const noexcept
104   { 107   {
105   return value_ != 0; 108   return value_ != 0;
106   } 109   }
107   110  
108   /// Return the negated option value. 111   /// Return the negated option value.
109   bool operator!() const noexcept 112   bool operator!() const noexcept
110   { 113   {
111   return value_ == 0; 114   return value_ == 0;
112   } 115   }
113   116  
114   /// Return the protocol level for `setsockopt`/`getsockopt`. 117   /// Return the protocol level for `setsockopt`/`getsockopt`.
HITCBC 115 - 787 static constexpr int level() noexcept 118 + 777 constexpr int level(family) const noexcept
116   { 119   {
HITCBC 117   787 return Level; 120   777 return Level;
118   } 121   }
119   122  
120   /// Return the option name for `setsockopt`/`getsockopt`. 123   /// Return the option name for `setsockopt`/`getsockopt`.
HITCBC 121 - 787 static constexpr int name() noexcept 124 + 777 constexpr int name(family) const noexcept
122   { 125   {
HITCBC 123   787 return Name; 126   777 return Name;
124   } 127   }
125   128  
126   /// Return a pointer to the underlying storage. 129   /// Return a pointer to the underlying storage.
HITCBC 127 - 10 void* data() noexcept 130 + 10 void* data(family) noexcept
128   { 131   {
HITCBC 129   10 return &value_; 132   10 return &value_;
130   } 133   }
131   134  
132   /// Return a pointer to the underlying storage. 135   /// Return a pointer to the underlying storage.
HITCBC 133 - 24 void const* data() const noexcept 136 + 22 void const* data(family) const noexcept
134   { 137   {
HITCBC 135   24 return &value_; 138   22 return &value_;
136   } 139   }
137   140  
138   /// Return the size of the underlying storage. 141   /// Return the size of the underlying storage.
HITCBC 139 - 32 std::size_t size() const noexcept 142 + 30 std::size_t size(family) const noexcept
140   { 143   {
HITCBC 141   32 return sizeof(value_); 144   30 return sizeof(value_);
142   } 145   }
143   146  
144   /** Normalize after `getsockopt` returns fewer bytes than expected. 147   /** Normalize after `getsockopt` returns fewer bytes than expected.
145   148  
146   Windows Vista+ may write only 1 byte for boolean options. 149   Windows Vista+ may write only 1 byte for boolean options.
147   150  
148   @param s The number of bytes actually written by `getsockopt`. 151   @param s The number of bytes actually written by `getsockopt`.
149   */ 152   */
HITCBC 150 - 9 void resize(std::size_t s) noexcept 153 + 9 void resize(family, std::size_t s) noexcept
151   { 154   {
HITCBC 152   9 if (s == sizeof(char)) 155   9 if (s == sizeof(char))
HITCBC 153   1 value_ = *reinterpret_cast<unsigned char*>(&value_) ? 1 : 0; 156   1 value_ = *reinterpret_cast<unsigned char*>(&value_) ? 1 : 0;
HITCBC 154   9 } 157   9 }
155   }; 158   };
156   159  
157   /** A socket option with an integer value. 160   /** A socket option with an integer value.
158   161  
159   Models socket options whose underlying representation is a 162   Models socket options whose underlying representation is a
160   plain `int`. The option's protocol level and name are encoded 163   plain `int`. The option's protocol level and name are encoded
161   as template parameters. 164   as template parameters.
162   165  
163   This is the native (inline) variant that includes platform 166   This is the native (inline) variant that includes platform
164   headers. For a type-erased version that avoids platform 167   headers. For a type-erased version that avoids platform
165   includes, use `boost::corosio::socket_option` instead. 168   includes, use `boost::corosio::socket_option` instead.
166   169  
167   @par Example 170   @par Example
168   @par !example integer 171   @par !example integer
169   172  
170   @tparam Level The protocol level (e.g. `SOL_SOCKET`). 173   @tparam Level The protocol level (e.g. `SOL_SOCKET`).
171   @tparam Name The option name (e.g. `SO_RCVBUF`). 174   @tparam Name The option name (e.g. `SO_RCVBUF`).
172   */ 175   */
173   template<int Level, int Name> 176   template<int Level, int Name>
174   class integer 177   class integer
175   { 178   {
176   int value_ = 0; 179   int value_ = 0;
177   180  
178   public: 181   public:
179   /// Construct with default value (zero). 182   /// Construct with default value (zero).
HITGIC 180   integer() = default; 183   262 integer() = default;
181   184  
182   /** Construct with an explicit value. 185   /** Construct with an explicit value.
183   186  
184   @param v The option value. 187   @param v The option value.
185   */ 188   */
HITCBC 186   9 explicit integer(int v) noexcept : value_(v) {} 189   5 explicit integer(int v) noexcept : value_(v) {}
187   190  
188   /// Assign a new value. 191   /// Assign a new value.
189   integer& operator=(int v) noexcept 192   integer& operator=(int v) noexcept
190   { 193   {
191   value_ = v; 194   value_ = v;
192   return *this; 195   return *this;
193   } 196   }
194   197  
195   /// Return the option value. 198   /// Return the option value.
HITCBC 196   7 int value() const noexcept 199   5 int value() const noexcept
197   { 200   {
HITCBC 198   7 return value_; 201   5 return value_;
199   } 202   }
200   203  
201   /// Return the protocol level for `setsockopt`/`getsockopt`. 204   /// Return the protocol level for `setsockopt`/`getsockopt`.
HITCBC 202 - 153 static constexpr int level() noexcept 205 + 139 constexpr int level(family) const noexcept
203   { 206   {
HITCBC 204   153 return Level; 207   139 return Level;
205   } 208   }
206   209  
207   /// Return the option name for `setsockopt`/`getsockopt`. 210   /// Return the option name for `setsockopt`/`getsockopt`.
HITCBC 208 - 153 static constexpr int name() noexcept 211 + 139 constexpr int name(family) const noexcept
209   { 212   {
HITCBC 210   153 return Name; 213   139 return Name;
211   } 214   }
212   215  
213   /// Return a pointer to the underlying storage. 216   /// Return a pointer to the underlying storage.
HITCBC 214 - 6 void* data() noexcept 217 + 4 void* data(family) noexcept
215   { 218   {
HITCBC 216   6 return &value_; 219   4 return &value_;
217   } 220   }
218   221  
219   /// Return a pointer to the underlying storage. 222   /// Return a pointer to the underlying storage.
HITCBC 220 - 8 void const* data() const noexcept 223 + 4 void const* data(family) const noexcept
221   { 224   {
HITCBC 222   8 return &value_; 225   4 return &value_;
223   } 226   }
224   227  
225   /// Return the size of the underlying storage. 228   /// Return the size of the underlying storage.
HITCBC 226 - 14 std::size_t size() const noexcept 229 + 8 std::size_t size(family) const noexcept
227   { 230   {
HITCBC 228   14 return sizeof(value_); 231   8 return sizeof(value_);
229   } 232   }
230   233  
231   /** Normalize after `getsockopt` returns fewer bytes than expected. 234   /** Normalize after `getsockopt` returns fewer bytes than expected.
232   235  
233   @param s The number of bytes actually written by `getsockopt`. 236   @param s The number of bytes actually written by `getsockopt`.
234   */ 237   */
HITCBC 235 - 7 void resize(std::size_t s) noexcept 238 + 5 void resize(family, std::size_t s) noexcept
236   { 239   {
HITCBC 237   7 if (s == sizeof(char)) 240   5 if (s == sizeof(char))
HITCBC 238   1 value_ = 241   1 value_ =
HITCBC 239   1 static_cast<int>(*reinterpret_cast<unsigned char*>(&value_)); 242   1 static_cast<int>(*reinterpret_cast<unsigned char*>(&value_));
HITCBC 240   7 } 243   5 }
241   }; 244   };
242   245  
243   /** A boolean socket option with single-byte storage. 246   /** A boolean socket option with single-byte storage.
244   247  
245   Some BSD-derived kernels (macOS, FreeBSD) require certain IPv4 multicast 248   Some BSD-derived kernels (macOS, FreeBSD) require certain IPv4 multicast
246   options (`IP_MULTICAST_LOOP`) to be set with a one-byte value and return 249   options (`IP_MULTICAST_LOOP`) to be set with a one-byte value and return
247   `EINVAL` for the four-byte form that Linux accepts. This template 250   `EINVAL` for the four-byte form that Linux accepts. This template
248   provides `unsigned char` storage so the option works on every platform. 251   provides `unsigned char` storage so the option works on every platform.
249   252  
250   @tparam Level The protocol level. 253   @tparam Level The protocol level.
251   @tparam Name The option name. 254   @tparam Name The option name.
252   */ 255   */
253   template<int Level, int Name> 256   template<int Level, int Name>
254   class byte_boolean 257   class byte_boolean
255   { 258   {
256   unsigned char value_ = 0; 259   unsigned char value_ = 0;
257   260  
258   public: 261   public:
259   byte_boolean() = default; 262   byte_boolean() = default;
260   263  
ECB 261   2 explicit byte_boolean(bool v) noexcept : value_(v ? 1 : 0) {} 264   explicit byte_boolean(bool v) noexcept : value_(v ? 1 : 0) {}
262   265  
263   byte_boolean& operator=(bool v) noexcept 266   byte_boolean& operator=(bool v) noexcept
264   { 267   {
265   value_ = v ? 1 : 0; 268   value_ = v ? 1 : 0;
266   return *this; 269   return *this;
267   } 270   }
268   271  
ECB 269   2 bool value() const noexcept 272   bool value() const noexcept
270   { 273   {
ECB 271   2 return value_ != 0; 274   return value_ != 0;
272   } 275   }
273   explicit operator bool() const noexcept 276   explicit operator bool() const noexcept
274   { 277   {
275   return value_ != 0; 278   return value_ != 0;
276   } 279   }
277   bool operator!() const noexcept 280   bool operator!() const noexcept
278   { 281   {
279   return value_ == 0; 282   return value_ == 0;
280   } 283   }
281   284  
ECB 282 - 22 static constexpr int level() noexcept 285 + constexpr int level(family) const noexcept
283   { 286   {
ECB 284   22 return Level; 287   return Level;
285   } 288   }
ECB 286 - 22 static constexpr int name() noexcept 289 + constexpr int name(family) const noexcept
287   { 290   {
ECB 288   22 return Name; 291   return Name;
289   } 292   }
290   293  
ECB 291 - 2 void* data() noexcept 294 + void* data(family) noexcept
292   { 295   {
ECB 293   2 return &value_; 296   return &value_;
294   } 297   }
ECB 295 - 2 void const* data() const noexcept 298 + void const* data(family) const noexcept
296   { 299   {
ECB 297   2 return &value_; 300   return &value_;
298   } 301   }
ECB 299 - 4 std::size_t size() const noexcept 302 + std::size_t size(family) const noexcept
300   { 303   {
ECB 301   4 return sizeof(value_); 304   return sizeof(value_);
302   } 305   }
303   306  
ECB 304 - 2 void resize(std::size_t) noexcept {} 307 + void resize(family, std::size_t) noexcept {}
305   }; 308   };
306   309  
307   /** An integer socket option with single-byte storage. 310   /** An integer socket option with single-byte storage.
308   311  
309   Same rationale as `byte_boolean`: BSD-derived kernels require 312   Same rationale as `byte_boolean`: BSD-derived kernels require
310   `IP_MULTICAST_TTL` to be set with a one-byte value. Linux accepts 313   `IP_MULTICAST_TTL` to be set with a one-byte value. Linux accepts
311   one byte too, so single-byte storage is portable. Values are 314   one byte too, so single-byte storage is portable. Values are
312   truncated to the 0–255 range. 315   truncated to the 0–255 range.
313   316  
314   @tparam Level The protocol level. 317   @tparam Level The protocol level.
315   @tparam Name The option name. 318   @tparam Name The option name.
316   */ 319   */
317   template<int Level, int Name> 320   template<int Level, int Name>
318   class byte_integer 321   class byte_integer
319   { 322   {
320   unsigned char value_ = 0; 323   unsigned char value_ = 0;
321   324  
322   public: 325   public:
323   byte_integer() = default; 326   byte_integer() = default;
324   327  
ECB 325   2 explicit byte_integer(int v) noexcept 328   explicit byte_integer(int v) noexcept
ECB 326   2 : value_(static_cast<unsigned char>(v)) 329   : value_(static_cast<unsigned char>(v))
327   { 330   {
ECB 328   2 } 331   }
329   332  
330   byte_integer& operator=(int v) noexcept 333   byte_integer& operator=(int v) noexcept
331   { 334   {
332   value_ = static_cast<unsigned char>(v); 335   value_ = static_cast<unsigned char>(v);
333   return *this; 336   return *this;
334   } 337   }
335   338  
ECB 336   2 int value() const noexcept 339   int value() const noexcept
337   { 340   {
ECB 338   2 return value_; 341   return value_;
339   } 342   }
340   343  
ECB 341 - 12 static constexpr int level() noexcept 344 + constexpr int level(family) const noexcept
342   { 345   {
ECB 343   12 return Level; 346   return Level;
344   } 347   }
ECB 345 - 12 static constexpr int name() noexcept 348 + constexpr int name(family) const noexcept
346   { 349   {
ECB 347   12 return Name; 350   return Name;
348   } 351   }
349   352  
ECB 350 - 2 void* data() noexcept 353 + void* data(family) noexcept
351   { 354   {
ECB 352   2 return &value_; 355   return &value_;
353   } 356   }
ECB 354 - 2 void const* data() const noexcept 357 + void const* data(family) const noexcept
355   { 358   {
ECB 356   2 return &value_; 359   return &value_;
357   } 360   }
ECB 358 - 4 std::size_t size() const noexcept 361 + std::size_t size(family) const noexcept
359   { 362   {
ECB 360   4 return sizeof(value_); 363   return sizeof(value_);
361   } 364   }
362   365  
ECB 363 - 2 void resize(std::size_t) noexcept {} 366 + void resize(family, std::size_t) noexcept {}
364   }; 367   };
365   368  
366   /** The SO_LINGER socket option (native variant). 369   /** The SO_LINGER socket option (native variant).
367   370  
368   Controls behavior when closing a socket with unsent data. 371   Controls behavior when closing a socket with unsent data.
369   When enabled, `close()` blocks until pending data is sent 372   When enabled, `close()` blocks until pending data is sent
370   or the timeout expires. 373   or the timeout expires.
371   374  
372   This variant stores the platform's `struct linger` directly, 375   This variant stores the platform's `struct linger` directly,
373   avoiding the opaque-storage indirection of the type-erased 376   avoiding the opaque-storage indirection of the type-erased
374   version. 377   version.
375   378  
376   @par Example 379   @par Example
377   @par !example linger 380   @par !example linger
378   */ 381   */
379   class linger 382   class linger
380   { 383   {
381   struct ::linger value_{}; 384   struct ::linger value_{};
382   385  
383   public: 386   public:
384   /// Construct with default values (disabled, zero timeout). 387   /// Construct with default values (disabled, zero timeout).
HITCBC 385   215 linger() = default; 388   645 linger() = default;
386   389  
387   /** Construct with explicit values. 390   /** Construct with explicit values.
388   391  
389   @param enabled `true` to enable linger behavior on close. 392   @param enabled `true` to enable linger behavior on close.
390   @param timeout The linger timeout in seconds. 393   @param timeout The linger timeout in seconds.
391   */ 394   */
HITCBC 392   203 linger(bool enabled, int timeout) noexcept 395   203 linger(bool enabled, int timeout) noexcept
HITCBC 393   203 { 396   203 {
HITCBC 394   203 value_.l_onoff = enabled ? 1 : 0; 397   203 value_.l_onoff = enabled ? 1 : 0;
HITCBC 395   203 value_.l_linger = static_cast<decltype(value_.l_linger)>(timeout); 398   203 value_.l_linger = static_cast<decltype(value_.l_linger)>(timeout);
HITCBC 396   203 } 399   203 }
397   400  
398   /// Return whether linger is enabled. 401   /// Return whether linger is enabled.
HITCBC 399   22 bool enabled() const noexcept 402   22 bool enabled() const noexcept
400   { 403   {
HITCBC 401   22 return value_.l_onoff != 0; 404   22 return value_.l_onoff != 0;
402   } 405   }
403   406  
404   /// Set whether linger is enabled. 407   /// Set whether linger is enabled.
HITCBC 405   4 void enabled(bool v) noexcept 408   4 void enabled(bool v) noexcept
406   { 409   {
HITCBC 407   4 value_.l_onoff = v ? 1 : 0; 410   4 value_.l_onoff = v ? 1 : 0;
HITCBC 408   4 } 411   4 }
409   412  
410   /// Return the linger timeout in seconds. 413   /// Return the linger timeout in seconds.
HITCBC 411   20 int timeout() const noexcept 414   20 int timeout() const noexcept
412   { 415   {
HITCBC 413   20 return static_cast<int>(value_.l_linger); 416   20 return static_cast<int>(value_.l_linger);
414   } 417   }
415   418  
416   /// Set the linger timeout in seconds. 419   /// Set the linger timeout in seconds.
HITCBC 417   4 void timeout(int v) noexcept 420   4 void timeout(int v) noexcept
418   { 421   {
HITCBC 419   4 value_.l_linger = static_cast<decltype(value_.l_linger)>(v); 422   4 value_.l_linger = static_cast<decltype(value_.l_linger)>(v);
HITCBC 420   4 } 423   4 }
421   424  
422   /// Return the protocol level for `setsockopt`/`getsockopt`. 425   /// Return the protocol level for `setsockopt`/`getsockopt`.
HITCBC 423 - 217 static constexpr int level() noexcept 426 + 217 constexpr int level(family) const noexcept
424   { 427   {
HITCBC 425   217 return SOL_SOCKET; 428   217 return SOL_SOCKET;
426   } 429   }
427   430  
428   /// Return the option name for `setsockopt`/`getsockopt`. 431   /// Return the option name for `setsockopt`/`getsockopt`.
HITCBC 429 - 217 static constexpr int name() noexcept 432 + 217 constexpr int name(family) const noexcept
430   { 433   {
HITCBC 431   217 return SO_LINGER; 434   217 return SO_LINGER;
432   } 435   }
433   436  
434   /// Return a pointer to the underlying storage. 437   /// Return a pointer to the underlying storage.
HITCBC 435 - 241 void* data() noexcept 438 + 241 void* data(family) noexcept
436   { 439   {
HITCBC 437   241 return &value_; 440   241 return &value_;
438   } 441   }
439   442  
440   /// Return a pointer to the underlying storage. 443   /// Return a pointer to the underlying storage.
HITCBC 441 - 2 void const* data() const noexcept 444 + 2 void const* data(family) const noexcept
442   { 445   {
HITCBC 443   2 return &value_; 446   2 return &value_;
444   } 447   }
445   448  
446   /// Return the size of the underlying storage. 449   /// Return the size of the underlying storage.
HITCBC 447 - 456 std::size_t size() const noexcept 450 + 456 std::size_t size(family) const noexcept
448   { 451   {
HITCBC 449   456 return sizeof(value_); 452   456 return sizeof(value_);
450   } 453   }
451   454  
452   /** Normalize after `getsockopt`. 455   /** Normalize after `getsockopt`.
453   456  
454   No-op — `struct linger` is always returned at full size. 457   No-op — `struct linger` is always returned at full size.
455   458  
456   @param s The number of bytes actually written by `getsockopt`. 459   @param s The number of bytes actually written by `getsockopt`.
457   */ 460   */
458 - void resize(std::size_t) noexcept {} 461 + void resize(family, std::size_t) noexcept {}
459   }; 462   };
460   463  
461   /// Disable Nagle's algorithm (TCP_NODELAY). 464   /// Disable Nagle's algorithm (TCP_NODELAY).
462   using no_delay = boolean<IPPROTO_TCP, TCP_NODELAY>; 465   using no_delay = boolean<IPPROTO_TCP, TCP_NODELAY>;
463   466  
464   /// Enable periodic keepalive probes (SO_KEEPALIVE). 467   /// Enable periodic keepalive probes (SO_KEEPALIVE).
465   using keep_alive = boolean<SOL_SOCKET, SO_KEEPALIVE>; 468   using keep_alive = boolean<SOL_SOCKET, SO_KEEPALIVE>;
466   469  
467   /// Restrict an IPv6 socket to IPv6 only (IPV6_V6ONLY). 470   /// Restrict an IPv6 socket to IPv6 only (IPV6_V6ONLY).
468   using v6_only = boolean<IPPROTO_IPV6, IPV6_V6ONLY>; 471   using v6_only = boolean<IPPROTO_IPV6, IPV6_V6ONLY>;
469   472  
470   /// Allow local address reuse (SO_REUSEADDR). 473   /// Allow local address reuse (SO_REUSEADDR).
471   using reuse_address = boolean<SOL_SOCKET, SO_REUSEADDR>; 474   using reuse_address = boolean<SOL_SOCKET, SO_REUSEADDR>;
472   475  
473   /// Allow sending to broadcast addresses (SO_BROADCAST). 476   /// Allow sending to broadcast addresses (SO_BROADCAST).
474   using broadcast = boolean<SOL_SOCKET, SO_BROADCAST>; 477   using broadcast = boolean<SOL_SOCKET, SO_BROADCAST>;
475   478  
476   /// Set the receive buffer size (SO_RCVBUF). 479   /// Set the receive buffer size (SO_RCVBUF).
477   using receive_buffer_size = integer<SOL_SOCKET, SO_RCVBUF>; 480   using receive_buffer_size = integer<SOL_SOCKET, SO_RCVBUF>;
478   481  
479   /// Set the send buffer size (SO_SNDBUF). 482   /// Set the send buffer size (SO_SNDBUF).
480   using send_buffer_size = integer<SOL_SOCKET, SO_SNDBUF>; 483   using send_buffer_size = integer<SOL_SOCKET, SO_SNDBUF>;
481   484  
482   #ifdef SO_REUSEPORT 485   #ifdef SO_REUSEPORT
483   /// Allow multiple sockets to bind to the same port (SO_REUSEPORT). 486   /// Allow multiple sockets to bind to the same port (SO_REUSEPORT).
484   using reuse_port = boolean<SOL_SOCKET, SO_REUSEPORT>; 487   using reuse_port = boolean<SOL_SOCKET, SO_REUSEPORT>;
485   #endif 488   #endif
486   489  
487 - /// Enable loopback of outgoing multicast on IPv4 (IP_MULTICAST_LOOP). 490 + /** Enable loopback of outgoing multicast (IP_MULTICAST_LOOP /
488 - using multicast_loop_v4 = byte_boolean<IPPROTO_IP, IP_MULTICAST_LOOP>; 491 + IPV6_MULTICAST_LOOP).
489 -  
490 - /// Enable loopback of outgoing multicast on IPv6 (IPV6_MULTICAST_LOOP).  
491 - using multicast_loop_v6 = boolean<IPPROTO_IPV6, IPV6_MULTICAST_LOOP>;  
492 -  
493 - /// Set the multicast TTL for IPv4 (IP_MULTICAST_TTL).  
494 - using multicast_hops_v4 = byte_integer<IPPROTO_IP, IP_MULTICAST_TTL>;  
495 -  
496 - /// Set the multicast hop limit for IPv6 (IPV6_MULTICAST_HOPS).  
497 - using multicast_hops_v6 = integer<IPPROTO_IPV6, IPV6_MULTICAST_HOPS>;  
498 -  
499 - /// Set the outgoing interface for IPv6 multicast (IPV6_MULTICAST_IF).  
500 - using multicast_interface_v6 = integer<IPPROTO_IPV6, IPV6_MULTICAST_IF>;  
501 -  
502 - /** Join an IPv4 multicast group (IP_ADD_MEMBERSHIP).  
503   492  
504 - @par Example 493 + The socket's family selects the wire rendering: a single byte
505 - @par !example join_group_v4 494 + at `IPPROTO_IP` for IPv4 (BSD-derived kernels reject the
  495 + four-byte form), an `int` at `IPPROTO_IPV6` for IPv6.
506   */ 496   */
507 - class join_group_v4 497 + class multicast_loop
508   { 498   {
509 - struct ip_mreq value_{}; 499 + unsigned char byte_ = 0; // IPv4 rendering
  500 + int int_ = 0; // IPv6 rendering
510   501  
511   public: 502   public:
512 - /// Construct with default values. 503 + /// Construct with default value (disabled).
HITCBC 513 - 4 join_group_v4() = default; 504 + 68 multicast_loop() = default;
514   505  
515 - /** Construct with a group and optional interface address. 506 + /** Construct with an explicit value.
516   507  
517 - @param group The multicast group address to join. 508 + @param v `true` to enable loopback, `false` to disable.
518 - @param iface The local interface to use (default: any).  
519   */ 509   */
HITCBC 520 - 6 join_group_v4( 510 + 6 explicit multicast_loop(bool v) noexcept : byte_(v ? 1 : 0), int_(v ? 1 : 0)
521 - ipv4_address group, ipv4_address iface = ipv4_address()) noexcept  
ECB 522   6 { 511   {
HITCBC 523 - 6 auto gb = group.to_bytes(); 512 + 6 }
ECB 524 - 6 auto ib = iface.to_bytes(); 513 +
ECB 525 - 6 std::memcpy(&value_.imr_multiaddr, gb.data(), 4); 514 + /// Assign a new value.
ECB 526 - 6 std::memcpy(&value_.imr_interface, ib.data(), 4); 515 + multicast_loop& operator=(bool v) noexcept
  516 + {
  517 + byte_ = v ? 1 : 0;
  518 + int_ = v ? 1 : 0;
  519 + return *this;
  520 + }
  521 +
  522 + /// Return the option value.
HITGNC   523 + 2 bool value() const noexcept
  524 + {
HITGNC   525 + 2 return byte_ != 0 || int_ != 0;
ECB 527   6 } 526   }
528   527  
529   /// Return the protocol level for `setsockopt`/`getsockopt`. 528   /// Return the protocol level for `setsockopt`/`getsockopt`.
HITCBC 530 - 8 static constexpr int level() noexcept 529 + 40 constexpr int level(family f) const noexcept
531   { 530   {
HITCBC 532 - 8 return IPPROTO_IP; 531 + 40 return f == family::v6 ? IPPROTO_IPV6 : IPPROTO_IP;
533   } 532   }
534   533  
535   /// Return the option name for `setsockopt`/`getsockopt`. 534   /// Return the option name for `setsockopt`/`getsockopt`.
HITCBC 536 - 8 static constexpr int name() noexcept 535 + 40 constexpr int name(family f) const noexcept
537   { 536   {
HITCBC 538 - 8 return IP_ADD_MEMBERSHIP; 537 + 40 return f == family::v6 ? IPV6_MULTICAST_LOOP : IP_MULTICAST_LOOP;
539   } 538   }
540   539  
541 - /// Return a pointer to the underlying storage. 540 + /// Return a pointer to the rendering for `f`.
HITCBC 542 - 6 void* data() noexcept 541 + 2 void* data(family f) noexcept
543   { 542   {
HITCBC 544 - 6 return &value_; 543 + 2 return f == family::v6 ? static_cast<void*>(&int_)
HITGNC   544 + 2 : static_cast<void*>(&byte_);
545   } 545   }
546   546  
547 - /// Return a pointer to the underlying storage. 547 + /// Return a pointer to the rendering for `f`.
HITCBC 548 - 2 void const* data() const noexcept 548 + 4 void const* data(family f) const noexcept
549   { 549   {
HITCBC 550 - 2 return &value_; 550 + 4 return f == family::v6 ? static_cast<void const*>(&int_)
HITGNC   551 + 4 : static_cast<void const*>(&byte_);
551   } 552   }
552   553  
553 - /// Return the size of the underlying storage. 554 + /// Return the size of the rendering for `f`.
HITCBC 554 - 12 std::size_t size() const noexcept 555 + 10 std::size_t size(family f) const noexcept
555   { 556   {
HITCBC 556 - 12 return sizeof(value_); 557 + 10 return f == family::v6 ? sizeof(int_) : sizeof(byte_);
557   } 558   }
558   559  
559 - /// No-op resize. 560 + /** Synchronize both renderings after `getsockopt`.
560 - void resize(std::size_t) noexcept {} 561 +
  562 + Only the rendering the socket's family selected was
  563 + written; fold it into the other so `value()` answers
  564 + from either.
  565 +
  566 + @param f The family `getsockopt` was performed for.
  567 + */
HITGNC   568 + 2 void resize(family f, std::size_t) noexcept
  569 + {
HITGNC   570 + 2 if (f == family::v6)
MISUNC   571 + ✗ byte_ = int_ ? 1 : 0;
  572 + else
HITGNC   573 + 2 int_ = byte_ ? 1 : 0;
HITGNC   574 + 2 }
561   }; 575   };
562   576  
563 - /** Leave an IPv4 multicast group (IP_DROP_MEMBERSHIP). 577 + /** Set the multicast TTL / hop limit (IP_MULTICAST_TTL /
  578 + IPV6_MULTICAST_HOPS).
564   579  
565 - @par Example 580 + The socket's family selects the wire rendering: a single byte
566 - @par !example leave_group_v4 581 + at `IPPROTO_IP` for IPv4, an `int` at `IPPROTO_IPV6` for IPv6.
567   */ 582   */
568 - class leave_group_v4 583 + class multicast_hops
569   { 584   {
570 - struct ip_mreq value_{}; 585 + unsigned char byte_ = 0; // IPv4 rendering
  586 + int int_ = 0; // IPv6 rendering
571   587  
572   public: 588   public:
573 - /// Construct with default values. 589 + /// Construct with default value (zero).
HITCBC 574 - 2 leave_group_v4() = default; 590 + 32 multicast_hops() = default;
575   591  
576 - /** Construct with a group and optional interface address. 592 + /** Construct with an explicit value.
577   593  
578 - @param group The multicast group address to leave. 594 + @param v The hop count, 0 to 255 — the range the IPv4 wire
579 - @param iface The local interface (default: any). 595 + rendering can carry.
  596 +
  597 + @throws std::logic_error if `v` is outside [0, 255].
580   */ 598   */
HITCBC 581 - 4 leave_group_v4( 599 + 6 explicit multicast_hops(int v)
582 - ipv4_address group, ipv4_address iface = ipv4_address()) noexcept  
HITCBC 583   4 { 600   6 {
HITCBC 584 - 4 auto gb = group.to_bytes(); 601 + 6 if (v < 0 || v > 255)
HITCBC 585 - 4 auto ib = iface.to_bytes(); 602 + 2 detail::throw_logic_error("multicast hops value out of range");
HITCBC 586 - 4 std::memcpy(&value_.imr_multiaddr, gb.data(), 4); 603 + 4 byte_ = static_cast<unsigned char>(v);
HITCBC 587 - 4 std::memcpy(&value_.imr_interface, ib.data(), 4); 604 + 4 int_ = v;
HITCBC 588   4 } 605   4 }
589   606  
590 - /// Return the protocol level for `setsockopt`/`getsockopt`. 607 + /** Assign a new value.
DCB 591 - 6 static constexpr int level() noexcept  
592 - {  
DCB 593 - 6 return IPPROTO_IP;  
594 - }  
595   608  
596 - /// Return the option name for `setsockopt`/`getsockopt`. 609 + @throws std::logic_error if `v` is outside [0, 255].
ECB 597 - 6 static constexpr int name() noexcept 610 + */
  611 + multicast_hops& operator=(int v)
598   { 612   {
ECB 599 - 6 return IP_DROP_MEMBERSHIP; 613 + if (v < 0 || v > 255)
  614 + detail::throw_logic_error("multicast hops value out of range");
  615 + byte_ = static_cast<unsigned char>(v);
  616 + int_ = v;
  617 + return *this;
600   } 618   }
601   619  
602 - /// Return a pointer to the underlying storage. 620 + /// Return the option value.
HITCBC 603 - 4 void* data() noexcept 621 + 2 int value() const noexcept
604   { 622   {
HITCBC 605 - 4 return &value_; 623 + 2 return int_;
606   } 624   }
607   625  
608 - /// Return a pointer to the underlying storage. 626 + /// Return the protocol level for `setsockopt`/`getsockopt`.
HITCBC 609 - 2 void const* data() const noexcept 627 + 22 constexpr int level(family f) const noexcept
610   { 628   {
HITCBC 611 - 2 return &value_; 629 + 22 return f == family::v6 ? IPPROTO_IPV6 : IPPROTO_IP;
612   } 630   }
613   631  
614 - /// Return the size of the underlying storage. 632 + /// Return the option name for `setsockopt`/`getsockopt`.
HITCBC 615 - 8 std::size_t size() const noexcept 633 + 22 constexpr int name(family f) const noexcept
616   { 634   {
HITCBC 617 - 8 return sizeof(value_); 635 + 22 return f == family::v6 ? IPV6_MULTICAST_HOPS : IP_MULTICAST_TTL;
618   } 636   }
619   637  
620 - /// No-op resize. 638 + /// Return a pointer to the rendering for `f`.
HITGIC 621 - void resize(std::size_t) noexcept {} 639 + 2 void* data(family f) noexcept
622 - };  
623 -  
624 - /** Join an IPv6 multicast group (IPV6_JOIN_GROUP).  
625 -  
626 - @par Example  
627 - @par !example join_group_v6  
628 - */  
629 - class join_group_v6  
630 - {  
631 - struct ipv6_mreq value_{};  
632 -  
633 - public:  
634 - /// Construct with default values.  
DCB 635 - 4 join_group_v6() = default;  
636 -  
637 - /** Construct with a group and optional interface index.  
638 -  
639 - @param group The multicast group address to join.  
640 - @param if_index The interface index (0 = kernel chooses).  
641 - */  
DCB 642 - 6 join_group_v6(ipv6_address group, unsigned int if_index = 0) noexcept  
ECB 643   6 { 640   {
HITCBC 644 - 6 auto gb = group.to_bytes(); 641 + 2 return f == family::v6 ? static_cast<void*>(&int_)
HITCBC 645 - 6 std::memcpy(&value_.ipv6mr_multiaddr, gb.data(), 16); 642 + 2 : static_cast<void*>(&byte_);
DCB 646 - 6 value_.ipv6mr_interface = if_index;  
ECB 647   6 } 643   }
648   644  
649 - /// Return the protocol level for `setsockopt`/`getsockopt`. 645 + /// Return a pointer to the rendering for `f`.
HITCBC 650 - 8 static constexpr int level() noexcept 646 + 4 void const* data(family f) const noexcept
651   { 647   {
HITCBC 652 - 8 return IPPROTO_IPV6; 648 + 4 return f == family::v6 ? static_cast<void const*>(&int_)
HITGNC   649 + 4 : static_cast<void const*>(&byte_);
653   } 650   }
654   651  
655 - /// Return the option name for `setsockopt`/`getsockopt`. 652 + /// Return the size of the rendering for `f`.
HITCBC 656 - 8 static constexpr int name() noexcept 653 + 6 std::size_t size(family f) const noexcept
657   { 654   {
HITCBC 658 - 8 return IPV6_JOIN_GROUP; 655 + 6 return f == family::v6 ? sizeof(int_) : sizeof(byte_);
659   } 656   }
660   657  
661 - /// Return a pointer to the underlying storage. 658 + /** Synchronize both renderings after `getsockopt`.
ECB 662 - 6 void* data() noexcept 659 +
  660 + @param f The family `getsockopt` was performed for.
  661 + */
HITGNC   662 + 2 void resize(family f, std::size_t) noexcept
663   { 663   {
HITCBC 664 - 6 return &value_; 664 + 2 if (f == family::v6)
MISUNC   665 + ✗ byte_ = static_cast<unsigned char>(int_);
  666 + else
HITGNC   667 + 2 int_ = byte_;
HITGIC 665   } 668   2 }
  669 + };
666   670  
667 - /// Return a pointer to the underlying storage. 671 + /** A multicast membership request.
ECB 668 - 2 void const* data() const noexcept 672 +
  673 + The group's family — not the socket's — selects the wire
  674 + struct and protocol level: a v4 group renders as an `ip_mreq`
  675 + at the IPv4 level even when applied to a dual-stack v6 socket,
  676 + which is the level such a join actually targets.
  677 +
  678 + @tparam Level4 The IPv4 protocol level.
  679 + @tparam Name4 The IPv4 option name.
  680 + @tparam Level6 The IPv6 protocol level.
  681 + @tparam Name6 The IPv6 option name.
  682 + */
  683 + template<int Level4, int Name4, int Level6, int Name6>
  684 + class membership_request
  685 + {
  686 + struct ip_mreq v4_{};
  687 + struct ipv6_mreq v6_{};
  688 + family group_family_ = family::v4;
  689 +
HITGNC   690 + 88 void assign_v4(ipv4_address group, ipv4_address iface) noexcept
669   { 691   {
HITCBC 670 - 2 return &value_; 692 + 88 auto g = group.to_bytes();
HITGNC   693 + 88 std::memcpy(&v4_.imr_multiaddr, g.data(), 4);
HITGNC   694 + 88 auto i = iface.to_bytes();
HITGNC   695 + 88 std::memcpy(&v4_.imr_interface, i.data(), 4);
HITGNC   696 + 88 group_family_ = family::v4;
HITGIC 671   } 697   88 }
672   698  
HITGIC 673 - /// Return the size of the underlying storage. 699 + 90 void assign_v6(ipv6_address const& group, unsigned int if_index) noexcept
DCB 674 - 12 std::size_t size() const noexcept  
675   { 700   {
HITCBC 676 - 12 return sizeof(value_); 701 + 90 auto g = group.to_bytes();
HITGNC   702 + 90 std::memcpy(&v6_.ipv6mr_multiaddr, g.data(), 16);
  703 + // The group's zone is the natural default interface
HITGNC   704 + 90 v6_.ipv6mr_interface = if_index ? if_index : group.scope_id();
HITGNC   705 + 90 group_family_ = family::v6;
HITGIC 677   } 706   90 }
678   707  
679 - /// No-op resize. 708 + public:
680 - void resize(std::size_t) noexcept {} 709 + /// Construct with default values.
681 - }; 710 + membership_request() = default;
682   711  
683 - /** Leave an IPv6 multicast group (IPV6_LEAVE_GROUP). 712 + /** Construct from a group address.
684   713  
685 - @par Example 714 + The group's family selects the wire representation; the
686 - @par !example leave_group_v6 715 + interface defaults to any (v4) or the group's zone (v6).
687 - */  
688 - class leave_group_v6  
689 - {  
690 - struct ipv6_mreq value_{};  
691   716  
692 - public: 717 + @param group The multicast group address.
693 - /// Construct with default values. 718 + */
HITCBC 694 - 4 leave_group_v6() = default; 719 + 16 explicit membership_request(ip_address const& group) noexcept
HITGNC   720 + 16 {
HITGNC   721 + 16 if (group.is_v4())
HITGNC   722 + 8 assign_v4(group.to_v4(), ipv4_address());
  723 + else
HITGNC   724 + 8 assign_v6(group.to_v6(), 0);
HITGNC   725 + 16 }
695   726  
696 - /** Construct with a group and optional interface index. 727 + /** Construct from an IPv4 group and interface address.
697   728  
698 - @param group The multicast group address to leave. 729 + @param group The multicast group address.
699 - @param if_index The interface index (0 = kernel chooses). 730 + @param iface The local interface to use (default: any).
700   */ 731   */
HITCBC 701 - 6 leave_group_v6(ipv6_address group, unsigned int if_index = 0) noexcept 732 + 80 membership_request(
  733 + ipv4_address group, ipv4_address iface = ipv4_address()) noexcept
HITCBC 702   6 { 734   80 {
HITCBC 703 - 6 auto gb = group.to_bytes(); 735 + 80 assign_v4(group, iface);
DCB 704 - 6 std::memcpy(&value_.ipv6mr_multiaddr, gb.data(), 16);  
DCB 705 - 6 value_.ipv6mr_interface = if_index;  
HITCBC 706   6 } 736   80 }
707   737  
708 - /// Return the protocol level for `setsockopt`/`getsockopt`. 738 + /** Construct from an IPv6 group and interface index.
ECB 709 - 8 static constexpr int level() noexcept 739 +
  740 + @param group The multicast group address.
  741 + @param if_index The interface index; 0 uses the group's
  742 + zone, and a zone of 0 lets the kernel choose.
  743 + */
HITGNC   744 + 82 membership_request(
  745 + ipv6_address const& group, unsigned int if_index = 0) noexcept
HITGIC 710   { 746   82 {
HITCBC 711 - 8 return IPPROTO_IPV6; 747 + 82 assign_v6(group, if_index);
HITGIC 712   } 748   82 }
713   749  
714 - /// Return the option name for `setsockopt`/`getsockopt`. 750 + /// Return the protocol level for the group's family.
HITCBC 715 - 8 static constexpr int name() noexcept 751 + 78 int level(family) const noexcept
716   { 752   {
HITCBC 717 - 8 return IPV6_LEAVE_GROUP; 753 + 78 return group_family_ == family::v4 ? Level4 : Level6;
718   } 754   }
719   755  
720 - /// Return a pointer to the underlying storage. 756 + /// Return the option name for the group's family.
HITCBC 721 - 6 void* data() noexcept 757 + 66 int name(family) const noexcept
722   { 758   {
HITCBC 723 - 6 return &value_; 759 + 66 return group_family_ == family::v4 ? Name4 : Name6;
724   } 760   }
725   761  
726 - /// Return a pointer to the underlying storage. 762 + /// Return a pointer to the wire struct for the group's family.
HITCBC 727 - 2 void const* data() const noexcept 763 + 56 void const* data(family) const noexcept
728   { 764   {
HITCBC 729 - 2 return &value_; 765 + 56 return group_family_ == family::v4 ? static_cast<void const*>(&v4_)
HITGNC   766 + 56 : static_cast<void const*>(&v6_);
730   } 767   }
731   768  
732 - /// Return the size of the underlying storage. 769 + /// Return the size of the wire struct for the group's family.
HITCBC 733 - 12 std::size_t size() const noexcept 770 + 104 std::size_t size(family) const noexcept
734   { 771   {
HITCBC 735 - 12 return sizeof(value_); 772 + 104 return group_family_ == family::v4 ? sizeof(v4_) : sizeof(v6_);
736   } 773   }
737   774  
738   /// No-op resize. 775   /// No-op resize.
739 - void resize(std::size_t) noexcept {} 776 + void resize(family, std::size_t) noexcept {}
740   }; 777   };
741   778  
742 - /** Set the outgoing interface for IPv4 multicast (IP_MULTICAST_IF). 779 + /// Join a multicast group (IP_ADD_MEMBERSHIP / IPV6_JOIN_GROUP).
  780 + using join_group = membership_request<
  781 + IPPROTO_IP,
  782 + IP_ADD_MEMBERSHIP,
  783 + IPPROTO_IPV6,
  784 + IPV6_JOIN_GROUP>;
743   785  
744 - Unlike the integer-based `multicast_interface_v6`, this option 786 + /// Leave a multicast group (IP_DROP_MEMBERSHIP / IPV6_LEAVE_GROUP).
745 - takes an `ipv4_address` identifying the local interface. 787 + using leave_group = membership_request<
  788 + IPPROTO_IP,
  789 + IP_DROP_MEMBERSHIP,
  790 + IPPROTO_IPV6,
  791 + IPV6_LEAVE_GROUP>;
746   792  
747 - @par Example 793 + /** Set the outgoing multicast interface (IP_MULTICAST_IF /
748 - @par !example multicast_interface_v4 794 + IPV6_MULTICAST_IF).
  795 +
  796 + The two families name interfaces differently on the wire — IPv4
  797 + by interface address, IPv6 by interface index — so the option
  798 + stores both renderings and the socket's family selects one; the
  799 + other stays at its default (any address, kernel-chosen index).
749   */ 800   */
750 - class multicast_interface_v4 801 + class multicast_interface
751   { 802   {
752 - struct in_addr value_{}; 803 + struct in_addr v4_{};
  804 + unsigned int if_index_ = 0;
753   805  
754   public: 806   public:
755 - /// Construct with default values (INADDR_ANY). 807 + /// Construct with default values (any address, kernel-chosen index).
HITCBC 756 - 2 multicast_interface_v4() = default; 808 + 22 multicast_interface() = default;
757   809  
758 - /** Construct with an interface address. 810 + /** Construct with an IPv4 interface address.
759   811  
760   @param iface The local interface address. 812   @param iface The local interface address.
761   */ 813   */
HITCBC 762 - 4 explicit multicast_interface_v4(ipv4_address iface) noexcept 814 + 8 explicit multicast_interface(ipv4_address iface) noexcept
HITCBC 763   4 { 815   8 {
HITCBC 764   4 auto b = iface.to_bytes(); 816   8 auto b = iface.to_bytes();
HITCBC 765 - 4 std::memcpy(&value_, b.data(), 4); 817 + 8 std::memcpy(&v4_, b.data(), 4);
HITGNC   818 + 8 }
  819 +
  820 + /** Construct with an IPv6 interface index.
  821 +
  822 + @param if_index The interface index (0 = kernel chooses).
  823 + */
HITGNC   824 + 4 explicit multicast_interface(unsigned int if_index) noexcept
HITGNC   825 + 4 : if_index_(if_index)
  826 + {
HITGNC   827 + 4 }
  828 +
  829 + /// Return the IPv4 rendering as an address.
HITGNC   830 + 2 ipv4_address address() const noexcept
  831 + {
  832 + ipv4_address::bytes_type b;
HITGNC   833 + 2 std::memcpy(b.data(), &v4_, 4);
HITGNC   834 + 2 return ipv4_address(b);
  835 + }
  836 +
  837 + /// Return the IPv6 rendering as an interface index.
HITGNC   838 + 4 unsigned int if_index() const noexcept
  839 + {
HITGNC   840 + 4 return if_index_;
ECB 766   4 } 841   }
767   842  
768   /// Return the protocol level for `setsockopt`/`getsockopt`. 843   /// Return the protocol level for `setsockopt`/`getsockopt`.
HITCBC 769 - 6 static constexpr int level() noexcept 844 + 14 constexpr int level(family f) const noexcept
770   { 845   {
HITCBC 771 - 6 return IPPROTO_IP; 846 + 14 return f == family::v6 ? IPPROTO_IPV6 : IPPROTO_IP;
772   } 847   }
773   848  
774   /// Return the option name for `setsockopt`/`getsockopt`. 849   /// Return the option name for `setsockopt`/`getsockopt`.
HITCBC 775 - 6 static constexpr int name() noexcept 850 + 14 constexpr int name(family f) const noexcept
776   { 851   {
HITCBC 777 - 6 return IP_MULTICAST_IF; 852 + 14 return f == family::v6 ? IPV6_MULTICAST_IF : IP_MULTICAST_IF;
778   } 853   }
779   854  
780 - /// Return a pointer to the underlying storage. 855 + /// Return a pointer to the rendering for `f`.
HITCBC 781 - 4 void* data() noexcept 856 + 10 void* data(family f) noexcept
782   { 857   {
HITCBC 783 - 4 return &value_; 858 + 10 return f == family::v6 ? static_cast<void*>(&if_index_)
HITGNC   859 + 10 : static_cast<void*>(&v4_);
784   } 860   }
785   861  
786 - /// Return a pointer to the underlying storage. 862 + /// Return a pointer to the rendering for `f`.
HITCBC 787 - 2 void const* data() const noexcept 863 + 4 void const* data(family f) const noexcept
788   { 864   {
HITCBC 789 - 2 return &value_; 865 + 4 return f == family::v6 ? static_cast<void const*>(&if_index_)
HITGNC   866 + 4 : static_cast<void const*>(&v4_);
790   } 867   }
791   868  
792 - /// Return the size of the underlying storage. 869 + /// Return the size of the rendering for `f`.
HITCBC 793 - 8 std::size_t size() const noexcept 870 + 24 std::size_t size(family f) const noexcept
794   { 871   {
HITCBC 795 - 8 return sizeof(value_); 872 + 24 return f == family::v6 ? sizeof(if_index_) : sizeof(v4_);
796   } 873   }
797   874  
798   /// No-op resize. 875   /// No-op resize.
HITGIC 799 - void resize(std::size_t) noexcept {} 876 + 2 void resize(family, std::size_t) noexcept {}
800   }; 877   };
801   878  
802   } // namespace boost::corosio::native_socket_option 879   } // namespace boost::corosio::native_socket_option
803   880  
804   #endif // BOOST_COROSIO_NATIVE_NATIVE_SOCKET_OPTION_HPP 881   #endif // BOOST_COROSIO_NATIVE_NATIVE_SOCKET_OPTION_HPP