diff --git a/groups/ntc/ntcd/ntcd_machine.h b/groups/ntc/ntcd/ntcd_machine.h index 2c98a9db..b8bc3646 100644 --- a/groups/ntc/ntcd/ntcd_machine.h +++ b/groups/ntc/ntcd/ntcd_machine.h @@ -871,7 +871,8 @@ class Session : public ntsi::DatagramSocket, /// Read data from the socket error queue. Then if the specified /// 'notifications' is not null parse fetched data to extract control /// messages into the specified 'notifications'. Return the error. - ntsa::Error receiveNotifications(ntsa::NotificationQueue* notifications); + ntsa::Error receiveNotifications(ntsa::NotificationQueue* notifications) + BSLS_KEYWORD_OVERRIDE; /// Shutdown the stream socket in the specified 'direction'. Return the /// error. diff --git a/groups/nts/ntsa/ntsa_notification.cpp b/groups/nts/ntsa/ntsa_notification.cpp index 865b8b23..977799dd 100644 --- a/groups/nts/ntsa/ntsa_notification.cpp +++ b/groups/nts/ntsa/ntsa_notification.cpp @@ -31,6 +31,9 @@ Notification::Notification(const Notification& original) new (d_timestamp.buffer()) ntsa::Timestamp(original.d_timestamp.object()); break; + case (ntsa::NotificationType::e_ZERO_COPY): + new (d_zeroCopy.buffer()) ntsa::ZeroCopy(original.d_zeroCopy.object()); + break; default: BSLS_ASSERT(d_type == ntsa::NotificationType::e_UNDEFINED); } @@ -50,6 +53,9 @@ Notification& Notification::operator=(const Notification& other) case (ntsa::NotificationType::e_TIMESTAMP): new (d_timestamp.buffer()) ntsa::Timestamp(other.d_timestamp.object()); break; + case (ntsa::NotificationType::e_ZERO_COPY): + new (d_zeroCopy.buffer()) ntsa::ZeroCopy(other.d_zeroCopy.object()); + break; default: BSLS_ASSERT(d_type == ntsa::NotificationType::e_UNDEFINED); } @@ -66,6 +72,8 @@ bool Notification::equals(const Notification& other) const switch (d_type) { case ntsa::NotificationType::e_TIMESTAMP: return d_timestamp.object() == other.d_timestamp.object(); + case ntsa::NotificationType::e_ZERO_COPY: + return d_zeroCopy.object() == other.d_zeroCopy.object(); default: return true; } @@ -80,6 +88,8 @@ bool Notification::less(const Notification& other) const switch (d_type) { case ntsa::NotificationType::e_TIMESTAMP: return d_timestamp.object() < other.d_timestamp.object(); + case ntsa::NotificationType::e_ZERO_COPY: + return d_zeroCopy.object() < other.d_zeroCopy.object(); default: return true; } @@ -93,6 +103,9 @@ bsl::ostream& Notification::print(bsl::ostream& stream, case ntsa::NotificationType::e_TIMESTAMP: d_timestamp.object().print(stream, level, spacesPerLevel); break; + case ntsa::NotificationType::e_ZERO_COPY: + d_zeroCopy.object().print(stream, level, spacesPerLevel); + break; default: BSLS_ASSERT(d_type == ntsa::NotificationType::e_UNDEFINED); stream << "UNDEFINED"; diff --git a/groups/nts/ntsa/ntsa_notification.h b/groups/nts/ntsa/ntsa_notification.h index 58fabcc8..e2f114e8 100644 --- a/groups/nts/ntsa/ntsa_notification.h +++ b/groups/nts/ntsa/ntsa_notification.h @@ -21,6 +21,7 @@ BSLS_IDENT("$Id: $") #include #include +#include #include #include #include @@ -31,8 +32,8 @@ namespace ntsa { /// Provide a union of notifications. /// /// @details -/// Provide a value-semantic type that represents a discriminated -/// union of notifications. +/// Provide a value-semantic type that represents a discriminated union of +/// notifications. // /// @par Thread Safety /// This class is not thread safe. @@ -42,6 +43,7 @@ class Notification { union { bsls::ObjectBuffer d_timestamp; + bsls::ObjectBuffer d_zeroCopy; }; ntsa::NotificationType::Value d_type; @@ -50,19 +52,18 @@ class Notification /// Create a new notification having an undefined type. Notification(); - /// Create a new notification the same value as the specified 'other' - /// object. + /// Create a new notification having the same value as the specified + /// 'other' object. Notification(const Notification& other); /// Destroy this object. ~Notification(); - /// Assign the value of the specified 'other' object to this object. - /// Return a reference to this modifiable object. + /// Assign the value of the specified 'other' object to this object. Return + /// a reference to this modifiable object. Notification& operator=(const Notification& other); - /// Reset the value of this object to its value upon default - /// construction. + /// Reset the value of this object to its value upon default construction. void reset(); /// Select the "timestamp" representation. Return a reference to the @@ -71,12 +72,24 @@ class Notification /// Select the "timestamp" representation initially having the specified /// 'value'. Return a reference to the modifiable representation. - ntsa::Timestamp& makeTimestamp(const ntsa::Timestamp& ts); + ntsa::Timestamp& makeTimestamp(const ntsa::Timestamp& value); - /// Return a reference to the modifiable "timestamp" representation. - /// The behavior is undefined unless 'isTimestamp()' is true. + /// Select the "zeroCopy" representation. Return a reference to the + /// modifiable representation. + ntsa::ZeroCopy& makeZeroCopy(); + + /// Select the "zeroCopy" representation initially having the specified + /// 'value'. Return a reference to the modifiable representation. + ntsa::ZeroCopy& makeZeroCopy(const ntsa::ZeroCopy& value); + + /// Return a reference to the "timestamp" representation. The behavior is + /// undefined unless 'isTimestamp()' is true. const ntsa::Timestamp& timestamp() const; + /// Return a reference to the "zeroCopy" representation. The behavior is + /// undefined unless 'isZeroCopy()' is true. + const ntsa::ZeroCopy& zeroCopy() const; + /// Return the type of the notification representation. ntsa::NotificationType::Value type() const; @@ -84,41 +97,44 @@ class Notification /// otherwise return false. bool isTimestamp() const; + /// Return true if the "zeroCopy" representation is currently selected, + /// otherwise return false. + bool isZeroCopy() const; + /// Return true if the notification representation is undefined, otherwise /// return false. bool isUndefined() const; - /// Return true if this object has the same value as the specified - /// 'other' object, otherwise return false. + /// Return true if this object has the same value as the specified 'other' + /// object, otherwise return false. bool equals(const Notification& other) const; - /// Return true if the value of this object is less than the value of - /// the specified 'other' object, otherwise return false. + /// Return true if the value of this object is less than the value of the + /// specified 'other' object, otherwise return false. bool less(const Notification& other) const; - /// Format this object to the specified output 'stream' at the - /// optionally specified indentation 'level' and return a reference to - /// the modifiable 'stream'. If 'level' is specified, optionally - /// specify 'spacesPerLevel', the number of spaces per indentation level - /// for this and all of its nested objects. Each line is indented by - /// the absolute value of 'level * spacesPerLevel'. If 'level' is - /// negative, suppress indentation of the first line. If - /// 'spacesPerLevel' is negative, suppress line breaks and format the - /// entire output on one line. If 'stream' is initially invalid, this - /// operation has no effect. Note that a trailing newline is provided - /// in multiline mode only. + /// Format this object to the specified output 'stream' at the optionally + /// specified indentation 'level' and return a reference to the modifiable + /// 'stream'. If 'level' is specified, optionally specify + /// 'spacesPerLevel', the number of spaces per indentation level for this + /// and all of its nested objects. Each line is indented by the absolute + /// value of 'level * spacesPerLevel'. If 'level' is negative, suppress + /// indentation of the first line. If 'spacesPerLevel' is negative, + /// suppress line breaks and format the entire output on one line. If + /// 'stream' is initially invalid, this operation has no effect. Note that + /// a trailing newline is provided in multiline mode only. bsl::ostream& print(bsl::ostream& stream, int level = 0, int spacesPerLevel = 4) const; - /// Defines the traits of this type. These traits can be used to select, - /// at compile-time, the most efficient algorithm to manipulate objects - /// of this type. + /// Defines the traits of this type. These traits can be used to select, at + /// compile-time, the most efficient algorithm to manipulate objects of + /// this type. NTSCFG_DECLARE_NESTED_BITWISE_MOVABLE_TRAITS(Notification); }; -/// Write the specified 'object' to the specified 'stream'. Return a -/// modifiable reference to the 'stream'. +/// Write the specified 'object' to the specified 'stream'. Return a modifiable +/// reference to the 'stream'. /// /// @related ntsa::Notification bsl::ostream& operator<<(bsl::ostream& stream, const Notification& object); @@ -141,8 +157,8 @@ bool operator!=(const Notification& lhs, const Notification& rhs); /// @related ntsa::Notification bool operator<(const Notification& lhs, const Notification& rhs); -/// Contribute the values of the salient attributes of the specified 'value' -/// to the specified hash 'algorithm'. +/// Contribute the values of the salient attributes of the specified 'value' to +/// the specified hash 'algorithm'. /// /// @related ntsa::Notification template @@ -181,20 +197,50 @@ ntsa::Timestamp& Notification::makeTimestamp() } NTSCFG_INLINE -ntsa::Timestamp& Notification::makeTimestamp(const ntsa::Timestamp& ts) +ntsa::Timestamp& Notification::makeTimestamp(const ntsa::Timestamp& value) { if (d_type == ntsa::NotificationType::e_TIMESTAMP) { - d_timestamp.object() = ts; + d_timestamp.object() = value; } else { this->reset(); - new (d_timestamp.buffer()) ntsa::Timestamp(ts); + new (d_timestamp.buffer()) ntsa::Timestamp(value); d_type = ntsa::NotificationType::e_TIMESTAMP; } return d_timestamp.object(); } +NTSCFG_INLINE +ntsa::ZeroCopy& Notification::makeZeroCopy() +{ + if (d_type == ntsa::NotificationType::e_ZERO_COPY) { + d_zeroCopy.object() = ntsa::ZeroCopy(); + } + else { + this->reset(); + new (d_zeroCopy.buffer()) ntsa::ZeroCopy(); + d_type = ntsa::NotificationType::e_ZERO_COPY; + } + + return d_zeroCopy.object(); +} + +NTSCFG_INLINE +ntsa::ZeroCopy& Notification::makeZeroCopy(const ntsa::ZeroCopy& value) +{ + if (d_type == ntsa::NotificationType::e_ZERO_COPY) { + d_zeroCopy.object() = value; + } + else { + this->reset(); + new (d_zeroCopy.buffer()) ntsa::ZeroCopy(value); + d_type = ntsa::NotificationType::e_ZERO_COPY; + } + + return d_zeroCopy.object(); +} + NTSCFG_INLINE const ntsa::Timestamp& Notification::timestamp() const { @@ -202,6 +248,13 @@ const ntsa::Timestamp& Notification::timestamp() const return d_timestamp.object(); } +NTSCFG_INLINE +const ntsa::ZeroCopy& Notification::zeroCopy() const +{ + BSLS_ASSERT(d_type == ntsa::NotificationType::e_ZERO_COPY); + return d_zeroCopy.object(); +} + NTSCFG_INLINE ntsa::NotificationType::Value Notification::type() const { @@ -214,6 +267,12 @@ bool Notification::isTimestamp() const return d_type == ntsa::NotificationType::e_TIMESTAMP; } +NTSCFG_INLINE +bool Notification::isZeroCopy() const +{ + return d_type == ntsa::NotificationType::e_ZERO_COPY; +} + NTSCFG_INLINE bool Notification::isUndefined() const { @@ -228,6 +287,9 @@ void hashAppend(HASH_ALGORITHM& algorithm, const Notification& value) if (value.isTimestamp()) { hashAppend(algorithm, value.timestamp()); } + else if (value.isZeroCopy()) { + hashAppend(algorithm, value.zeroCopy()); + } } } // close package namespace diff --git a/groups/nts/ntsa/ntsa_notification.t.cpp b/groups/nts/ntsa/ntsa_notification.t.cpp index 27b20d1b..d2dd1b6b 100644 --- a/groups/nts/ntsa/ntsa_notification.t.cpp +++ b/groups/nts/ntsa/ntsa_notification.t.cpp @@ -28,11 +28,13 @@ NTSCFG_TEST_CASE(1) Notification n; NTSCFG_TEST_TRUE(n.isUndefined()); NTSCFG_TEST_FALSE(n.isTimestamp()); + NTSCFG_TEST_FALSE(n.isZeroCopy()); NTSCFG_TEST_EQ(n.type(), NotificationType::e_UNDEFINED); Timestamp& ts = n.makeTimestamp(); NTSCFG_TEST_TRUE(n.isTimestamp()); NTSCFG_TEST_FALSE(n.isUndefined()); + NTSCFG_TEST_FALSE(n.isZeroCopy()); NTSCFG_TEST_EQ(n.type(), NotificationType::e_TIMESTAMP); ts.setId(id); @@ -90,6 +92,9 @@ NTSCFG_TEST_CASE(4) n1.makeTimestamp(); NTSCFG_TEST_NE(n1, n2); + + n2.makeZeroCopy(); + NTSCFG_TEST_NE(n1, n2); } NTSCFG_TEST_CASE(5) @@ -112,6 +117,43 @@ NTSCFG_TEST_CASE(5) NTSCFG_TEST_EQ(n2.timestamp(), t); } +NTSCFG_TEST_CASE(6) +{ + Notification n; + NTSCFG_TEST_TRUE(n.isUndefined()); + NTSCFG_TEST_FALSE(n.isZeroCopy()); + NTSCFG_TEST_EQ(n.type(), NotificationType::e_UNDEFINED); + + ZeroCopy& zc = n.makeZeroCopy(); + NTSCFG_TEST_TRUE(n.isZeroCopy()); + NTSCFG_TEST_FALSE(n.isUndefined()); + NTSCFG_TEST_FALSE(n.isTimestamp()); + NTSCFG_TEST_EQ(n.type(), NotificationType::e_ZERO_COPY); + + zc.setFrom(1); + zc.setTo(22); + zc.setCode(1); + NTSCFG_TEST_EQ(n.zeroCopy(), zc); +} + +NTSCFG_TEST_CASE(7) +{ + ZeroCopy zc; + zc.setFrom(1); + zc.setTo(22); + zc.setCode(1); + + Notification n; + n.makeZeroCopy(zc); + + NTSCFG_TEST_TRUE(n.isZeroCopy()); + + n.reset(); + NTSCFG_TEST_TRUE(n.isUndefined()); + NTSCFG_TEST_FALSE(n.isZeroCopy()); + NTSCFG_TEST_EQ(n.type(), NotificationType::e_UNDEFINED); +} + NTSCFG_TEST_DRIVER { NTSCFG_TEST_REGISTER(1); @@ -119,5 +161,7 @@ NTSCFG_TEST_DRIVER NTSCFG_TEST_REGISTER(3); NTSCFG_TEST_REGISTER(4); NTSCFG_TEST_REGISTER(5); + NTSCFG_TEST_REGISTER(6); + NTSCFG_TEST_REGISTER(7); } NTSCFG_TEST_DRIVER_END; diff --git a/groups/nts/ntsa/ntsa_notificationtype.cpp b/groups/nts/ntsa/ntsa_notificationtype.cpp index 6b191901..0b566a6d 100644 --- a/groups/nts/ntsa/ntsa_notificationtype.cpp +++ b/groups/nts/ntsa/ntsa_notificationtype.cpp @@ -28,6 +28,7 @@ int NotificationType::fromInt(NotificationType::Value* result, int number) switch (number) { case NotificationType::e_UNDEFINED: case NotificationType::e_TIMESTAMP: + case NotificationType::e_ZERO_COPY: *result = static_cast(number); return 0; default: @@ -46,6 +47,10 @@ int NotificationType::fromString(NotificationType::Value* result, *result = e_TIMESTAMP; return 0; } + if (bdlb::String::areEqualCaseless(string, "ZERO_COPY")) { + *result = e_ZERO_COPY; + return 0; + } return -1; } @@ -59,6 +64,9 @@ const char* NotificationType::toString(NotificationType::Value value) case e_TIMESTAMP: { return "TIMESTAMP"; } break; + case e_ZERO_COPY: { + return "ZERO_COPY"; + } break; } BSLS_ASSERT(!"invalid enumerator"); diff --git a/groups/nts/ntsa/ntsa_notificationtype.h b/groups/nts/ntsa/ntsa_notificationtype.h index 4a895741..f65e00de 100644 --- a/groups/nts/ntsa/ntsa_notificationtype.h +++ b/groups/nts/ntsa/ntsa_notificationtype.h @@ -34,7 +34,17 @@ namespace ntsa { struct NotificationType { public: /// Provide an enumeration of the notification types. - enum Value { e_UNDEFINED = 0, e_TIMESTAMP = 1 }; + enum Value { + /// The notification type is not defined. + e_UNDEFINED = 0, + + /// The notification describes a timestamp for outgoing data. + e_TIMESTAMP = 1, + + /// The notification describes the completion of one or more zero-copy + /// operations to enqueue data to the socket send buffer. + e_ZERO_COPY = 2 + }; /// Return the string representation exactly matching the enumerator /// name corresponding to the specified enumeration 'value'. diff --git a/groups/nts/ntsa/ntsa_notificationtype.t.cpp b/groups/nts/ntsa/ntsa_notificationtype.t.cpp index eac0453e..2d7ea138 100644 --- a/groups/nts/ntsa/ntsa_notificationtype.t.cpp +++ b/groups/nts/ntsa/ntsa_notificationtype.t.cpp @@ -30,6 +30,10 @@ NTSCFG_TEST_CASE(1) bsl::strcmp(NotificationType::toString(NotificationType::e_TIMESTAMP), "TIMESTAMP"), 0); + NTSCFG_TEST_EQ(bsl::strcmp(NotificationType::toString( + NotificationType::e_ZERO_COPY), + "ZERO_COPY"), + 0); } NTSCFG_TEST_CASE(2) @@ -45,14 +49,18 @@ NTSCFG_TEST_CASE(2) NTSCFG_TEST_EQ(NotificationType::fromInt(&v, 1), 0); NTSCFG_TEST_EQ(v, NotificationType::e_TIMESTAMP); - NTSCFG_TEST_EQ(NotificationType::fromInt(&v, 2), -1); - NTSCFG_TEST_EQ(v, NotificationType::e_TIMESTAMP); + NTSCFG_TEST_EQ(NotificationType::fromInt(&v, 2), 0); + NTSCFG_TEST_EQ(v, NotificationType::e_ZERO_COPY); + + NTSCFG_TEST_EQ(NotificationType::fromInt(&v, 3), -1); + NTSCFG_TEST_EQ(v, NotificationType::e_ZERO_COPY); } NTSCFG_TEST_CASE(3) { const bsl::string undefined = "undefined"; const bsl::string timestamp = "timestamp"; + const bsl::string zerocopy = "zero_copy"; const bsl::string random = "random_string"; NotificationType::Value v = NotificationType::e_TIMESTAMP; @@ -65,6 +73,9 @@ NTSCFG_TEST_CASE(3) NTSCFG_TEST_EQ(NotificationType::fromString(&v, timestamp), 0); NTSCFG_TEST_EQ(v, NotificationType::e_TIMESTAMP); + + NTSCFG_TEST_EQ(NotificationType::fromString(&v, zerocopy), 0); + NTSCFG_TEST_EQ(v, NotificationType::e_ZERO_COPY); } NTSCFG_TEST_CASE(4) @@ -72,9 +83,10 @@ NTSCFG_TEST_CASE(4) bsl::stringstream ss; ss << NotificationType::e_TIMESTAMP << ", " - << NotificationType::e_UNDEFINED; + << NotificationType::e_UNDEFINED << ", " + << NotificationType::e_ZERO_COPY; - NTSCFG_TEST_EQ(ss.str(), "TIMESTAMP, UNDEFINED"); + NTSCFG_TEST_EQ(ss.str(), "TIMESTAMP, UNDEFINED, ZERO_COPY"); } NTSCFG_TEST_DRIVER diff --git a/groups/nts/ntsa/ntsa_sendoptions.cpp b/groups/nts/ntsa/ntsa_sendoptions.cpp index 05410b66..2f419a05 100644 --- a/groups/nts/ntsa/ntsa_sendoptions.cpp +++ b/groups/nts/ntsa/ntsa_sendoptions.cpp @@ -28,7 +28,8 @@ bool SendOptions::equals(const SendOptions& other) const return (d_endpoint == other.d_endpoint && d_foreignHandle == other.d_foreignHandle && d_maxBytes == other.d_maxBytes && - d_maxBuffers == other.d_maxBuffers); + d_maxBuffers == other.d_maxBuffers && + d_zeroCopy == other.d_zeroCopy); } bool SendOptions::less(const SendOptions& other) const @@ -57,7 +58,14 @@ bool SendOptions::less(const SendOptions& other) const return false; } - return d_maxBuffers < other.d_maxBuffers; + if (d_maxBuffers < other.d_maxBuffers) { + return true; + } + + if (other.d_maxBuffers < d_maxBuffers) { + return false; + } + return d_zeroCopy < other.d_zeroCopy; } bsl::ostream& SendOptions::print(bsl::ostream& stream, @@ -70,6 +78,7 @@ bsl::ostream& SendOptions::print(bsl::ostream& stream, printer.printAttribute("foreignHandle", d_foreignHandle); printer.printAttribute("maxBytes", d_maxBytes); printer.printAttribute("maxBuffers", d_maxBuffers); + printer.printAttribute("zeroCopy", d_zeroCopy); printer.end(); return stream; } diff --git a/groups/nts/ntsa/ntsa_sendoptions.h b/groups/nts/ntsa/ntsa_sendoptions.h index 512572e3..1f27ee10 100644 --- a/groups/nts/ntsa/ntsa_sendoptions.h +++ b/groups/nts/ntsa/ntsa_sendoptions.h @@ -79,6 +79,16 @@ namespace ntsa { /// zero, or, for stream sockets only, to NTSCFG_DEFAULT_MAX_INPLACE_BUFFERS. /// Note that this value is currently only honored when sending blobs. /// +/// @li @b zeroCopy: +/// The flag to request the data-to-send be referenced in-place rather than +/// copied to the send buffer to the specified 'value'. Note that this flag is +/// advisory; the operating system may neither support nor decide to allow +/// zero-copy semantics. Regardless, if zero-copy semantics are requested the +/// application must ensure the data-to-send is neither overwritten nor +/// invalidated (i.e. freed) until the completion of the send operation is +/// indicated in a subsequent notification (which also indicates whether the +/// data was referenced in-place or copied.) +/// /// @par Thread Safety /// This class is not thread safe. /// @@ -89,6 +99,7 @@ class SendOptions bdlb::NullableValue d_foreignHandle; bsl::size_t d_maxBytes; bsl::size_t d_maxBuffers; + bool d_zeroCopy; public: /// Create new send options having the default value. @@ -117,24 +128,30 @@ class SendOptions // 'value'. void setForeignHandle(ntsa::Handle value); - /// Set the maximum number of bytes to copy to the specified 'value'. + /// Set the maximum number of bytes to send to the specified 'value'. void setMaxBytes(bsl::size_t value); - /// Set the maximum number of buffers to copy to the specified 'value'. + /// Set the maximum number of buffers to send to the specified 'value'. void setMaxBuffers(bsl::size_t value); + /// Set the flag to request zero-copy semantics to the specified 'value'. + void setZeroCopy(bool value); + /// Return the remote endpoint to which the data should be sent. const bdlb::NullableValue& endpoint() const; /// Return the handle to the open socket to send to the peer. const bdlb::NullableValue& foreignHandle() const; - /// Return the maximum number of bytes to copy. + /// Return the maximum number of bytes to send. bsl::size_t maxBytes() const; - /// Return the maximum number of buffers to copy. + /// Return the maximum number of buffers to send. bsl::size_t maxBuffers() const; + /// Return the flag that indicates zero-copy semantics are requested. + bool zeroCopy() const; + /// Return true if this object has the same value as the specified /// 'other' object, otherwise return false. bool equals(const SendOptions& other) const; @@ -201,6 +218,7 @@ SendOptions::SendOptions() , d_foreignHandle() , d_maxBytes(0) , d_maxBuffers(0) +, d_zeroCopy(false) { } @@ -210,6 +228,7 @@ SendOptions::SendOptions(const SendOptions& original) , d_foreignHandle(original.d_foreignHandle) , d_maxBytes(original.d_maxBytes) , d_maxBuffers(original.d_maxBuffers) +, d_zeroCopy(original.d_zeroCopy) { } @@ -225,6 +244,7 @@ SendOptions& SendOptions::operator=(const SendOptions& other) d_foreignHandle = other.d_foreignHandle; d_maxBytes = other.d_maxBytes; d_maxBuffers = other.d_maxBuffers; + d_zeroCopy = other.d_zeroCopy; return *this; } @@ -235,6 +255,7 @@ void SendOptions::reset() d_foreignHandle.reset(); d_maxBytes = 0; d_maxBuffers = 0; + d_zeroCopy = false; } NTSCFG_INLINE @@ -261,6 +282,12 @@ void SendOptions::setMaxBuffers(bsl::size_t value) d_maxBuffers = value; } +NTSCFG_INLINE +void SendOptions::setZeroCopy(bool value) +{ + d_zeroCopy = value; +} + NTSCFG_INLINE const bdlb::NullableValue& SendOptions::endpoint() const { @@ -285,6 +312,12 @@ bsl::size_t SendOptions::maxBuffers() const return d_maxBuffers; } +NTSCFG_INLINE +bool SendOptions::zeroCopy() const +{ + return d_zeroCopy; +} + NTSCFG_INLINE bsl::ostream& operator<<(bsl::ostream& stream, const SendOptions& object) { @@ -318,6 +351,7 @@ void hashAppend(HASH_ALGORITHM& algorithm, const SendOptions& value) hashAppend(algorithm, value.foreignHandle()); hashAppend(algorithm, value.maxBytes()); hashAppend(algorithm, value.maxBuffers()); + hashAppend(algorithm, value.zeroCopy()); } } // close package namespace diff --git a/groups/nts/ntsa/ntsa_socketconfig.cpp b/groups/nts/ntsa/ntsa_socketconfig.cpp index 15b905f9..4aa8b49d 100644 --- a/groups/nts/ntsa/ntsa_socketconfig.cpp +++ b/groups/nts/ntsa/ntsa_socketconfig.cpp @@ -73,6 +73,9 @@ void SocketConfig::setOption(const ntsa::SocketOption& option) else if (option.isTimestampOutgoingData()) { d_timestampOutgoingData = option.timestampOutgoingData(); } + else if (option.isZeroCopy()) { + d_zeroCopy = option.zeroCopy(); + } } void SocketConfig::getOption(ntsa::SocketOption* option, @@ -162,6 +165,11 @@ void SocketConfig::getOption(ntsa::SocketOption* option, option->makeTimestampOutgoingData(d_timestampOutgoingData.value()); } } + else if (type == ntsa::SocketOptionType::e_ZERO_COPY) { + if (!d_zeroCopy.isNull()) { + option->makeZeroCopy(d_zeroCopy.value()); + } + } } bool SocketConfig::equals(const SocketConfig& other) const @@ -179,7 +187,8 @@ bool SocketConfig::equals(const SocketConfig& other) const d_bypassRouting == other.d_bypassRouting && d_inlineOutOfBandData == other.d_inlineOutOfBandData && d_timestampIncomingData == other.d_timestampIncomingData && - d_timestampOutgoingData == other.d_timestampOutgoingData; + d_timestampOutgoingData == other.d_timestampOutgoingData && + d_zeroCopy == other.d_zeroCopy; } bool SocketConfig::less(const SocketConfig& other) const @@ -304,7 +313,15 @@ bool SocketConfig::less(const SocketConfig& other) const return false; } - return d_timestampOutgoingData < other.d_timestampOutgoingData; + if (d_timestampOutgoingData < other.d_timestampOutgoingData) { + return true; + } + + if (other.d_timestampOutgoingData < d_timestampOutgoingData) { + return false; + } + + return d_zeroCopy < other.d_zeroCopy; } bsl::ostream& SocketConfig::print(bsl::ostream& stream, @@ -313,24 +330,83 @@ bsl::ostream& SocketConfig::print(bsl::ostream& stream, { bslim::Printer printer(&stream, level, spacesPerLevel); printer.start(); - printer.printAttribute("reuseAddress", d_reuseAddress); - printer.printAttribute("d_keepAlive", d_keepAlive); - printer.printAttribute("d_cork", d_cork); - printer.printAttribute("d_delayTransmission", d_delayTransmission); - printer.printAttribute("d_delayAcknowledgement", d_delayAcknowledgement); - printer.printAttribute("d_sendBufferSize", d_sendBufferSize); - printer.printAttribute("d_sendBufferLowWatermark", - d_sendBufferLowWatermark); - printer.printAttribute("d_receiveBufferSize", d_receiveBufferSize); - printer.printAttribute("d_receiveBufferLowWatermark", - d_receiveBufferLowWatermark); - printer.printAttribute("d_debug", d_debug); - printer.printAttribute("d_linger", d_linger); - printer.printAttribute("d_broadcast", d_broadcast); - printer.printAttribute("d_bypassRouting", d_bypassRouting); - printer.printAttribute("d_inlineOutOfBandData", d_inlineOutOfBandData); - printer.printAttribute("d_timestampIncomingData", d_timestampIncomingData); - printer.printAttribute("d_timestampOutgoingData", d_timestampOutgoingData); + + if (!d_reuseAddress.isNull()) { + printer.printAttribute("reuseAddress", d_reuseAddress.value()); + } + + if (!d_keepAlive.isNull()) { + printer.printAttribute("keepAlive", d_keepAlive.value()); + } + + if (!d_cork.isNull()) { + printer.printAttribute("cork", d_cork.value()); + } + + if (!d_delayTransmission.isNull()) { + printer.printAttribute("delayTransmission", + d_delayTransmission.value()); + } + + if (!d_delayAcknowledgement.isNull()) { + printer.printAttribute("delayAcknowledgement", + d_delayAcknowledgement.value()); + } + + if (!d_sendBufferSize.isNull()) { + printer.printAttribute("sendBufferSize", d_sendBufferSize.value()); + } + + if (!d_sendBufferLowWatermark.isNull()) { + printer.printAttribute("sendBufferLowWatermark", + d_sendBufferLowWatermark.value()); + } + + if (!d_receiveBufferSize.isNull()) { + printer.printAttribute("receiveBufferSize", + d_receiveBufferSize.value()); + } + + if (!d_receiveBufferLowWatermark.isNull()) { + printer.printAttribute("receiveBufferLowWatermark", + d_receiveBufferLowWatermark.value()); + } + + if (!d_debug.isNull()) { + printer.printAttribute("debug", d_debug.value()); + } + + if (!d_linger.isNull()) { + printer.printAttribute("linger", d_linger.value()); + } + + if (!d_broadcast.isNull()) { + printer.printAttribute("broadcast", d_broadcast.value()); + } + + if (!d_bypassRouting.isNull()) { + printer.printAttribute("bypassRouting", d_bypassRouting.value()); + } + + if (!d_inlineOutOfBandData.isNull()) { + printer.printAttribute("inlineOutOfBandData", + d_inlineOutOfBandData.value()); + } + + if (!d_timestampIncomingData.isNull()) { + printer.printAttribute("timestampIncomingData", + d_timestampIncomingData.value()); + } + + if (!d_timestampOutgoingData.isNull()) { + printer.printAttribute("timestampOutgoingData", + d_timestampOutgoingData.value()); + } + + if (!d_zeroCopy.isNull()) { + printer.printAttribute("zeroCopy", d_zeroCopy.value()); + } + printer.end(); return stream; } diff --git a/groups/nts/ntsa/ntsa_socketconfig.h b/groups/nts/ntsa/ntsa_socketconfig.h index 9cdddc24..a03bf9e4 100644 --- a/groups/nts/ntsa/ntsa_socketconfig.h +++ b/groups/nts/ntsa/ntsa_socketconfig.h @@ -44,18 +44,26 @@ namespace ntsa { /// That flag that indicates the operating system implementation should /// periodically emit transport-level "keep-alive" packets. /// -/// @li @b cork: -/// TODO. +/// @li @b cork: +/// The flag that indicates that successive writes should be coalesced into the +/// largest packets that can be formed. When set to true, this option indicates +/// the user favors better network efficiency at the expense of worse latency. +/// When set to false, this option indicates the user favors better latency at +/// the expense of worse network efficiency. /// /// @li @b delayTransmission: -/// The flag that indicates that subsequent writes should be coalesced into the +/// The flag that indicates that successive writes should be coalesced into /// larger packets that would otherwise form. When set to true, this option -/// indicates the user favors smaller latency at the expense of less network -/// efficiency. When set to false, this option indicates the user favors -/// greater network efficiency at the expense of greater latency. +/// indicates the user favors better network efficiency at the expense of worse +/// latency. When set to false, this option indicates the user favors better +/// latency at the expense of worse network efficiency. /// /// @li @b delayAcknowledgement: -/// TODO. +/// The flag that indicates acknowledgement of successively-received packets +/// should be coalesced. When set to true, this option indicates the user +/// favors better network efficiency at the expense of worse latency. When set +/// to false, this option indicates the user favors better latency at the +/// expense of worse network efficiency. /// /// @li @b sendBufferSize: /// The maximum size of each socket send buffer. On some platforms, this @@ -94,6 +102,16 @@ namespace ntsa { /// The flag that indicates out-of-band data should be placed into the normal /// data input queue. /// +/// @li @b timestampIncomingData: +/// The flag that indicates timestamps should be generated for outgoing data. +/// +/// @li @b timestampOutgoingData: +/// The flag that indicates timestamps should be generated for incoming data. +/// +/// @li @b zeroCopy: +/// The flag that indicates each send operation can request copy avoidance when +/// enqueing data to the socket send buffer. +/// /// @par Thread Safety /// This class is not thread safe. /// @@ -116,6 +134,7 @@ class SocketConfig bdlb::NullableValue d_inlineOutOfBandData; bdlb::NullableValue d_timestampIncomingData; bdlb::NullableValue d_timestampOutgoingData; + bdlb::NullableValue d_zeroCopy; public: /// Create new send options having the default value. @@ -197,12 +216,19 @@ class SocketConfig /// the normal data input queue to the specified 'value'. void setInlineOutOfBandData(bool value); - /// Return the flag that indicates incoming data should be timestamped. + /// Set the flag that indicates incoming data should be timestamped to the + /// specified 'value'. void setTimestampIncomingData(bool value); - /// Set the flag that indicates outgoing data should be timestamped. + /// Set the flag that indicates outgoing data should be timestamped to the + /// specified 'value'. void setTimestampOutgoingData(bool value); + /// Set the flag that indicates each send operation can request copy + /// avoidance when enqueing data to the socket send buffer to the specified + /// 'value'. + void setZeroCopy(bool value); + /// Load into the specified 'option' the option for the specified /// 'type'. Note that if the option for the 'type' is not set, the /// resulting 'option->isUndefined()' will be true. @@ -267,6 +293,10 @@ class SocketConfig /// Return the flag that indicates outgoing data should be timestamped. const bdlb::NullableValue& timestampOutgoingData() const; + /// Return the flag that indicates each send operation can request copy + /// avoidance when enqueing data to the socket send buffer. + const bdlb::NullableValue& zeroCopy() const; + /// Return true if this object has the same value as the specified /// 'other' object, otherwise return false. bool equals(const SocketConfig& other) const; @@ -345,6 +375,7 @@ SocketConfig::SocketConfig() , d_inlineOutOfBandData() , d_timestampIncomingData() , d_timestampOutgoingData() +, d_zeroCopy() { } @@ -366,6 +397,7 @@ SocketConfig::SocketConfig(const SocketConfig& original) , d_inlineOutOfBandData(original.d_inlineOutOfBandData) , d_timestampIncomingData(original.d_timestampIncomingData) , d_timestampOutgoingData(original.d_timestampOutgoingData) +, d_zeroCopy(original.d_zeroCopy) { } @@ -393,6 +425,7 @@ SocketConfig& SocketConfig::operator=(const SocketConfig& other) d_inlineOutOfBandData = other.d_inlineOutOfBandData; d_timestampIncomingData = other.d_timestampIncomingData; d_timestampOutgoingData = other.d_timestampOutgoingData; + d_zeroCopy = other.d_zeroCopy; return *this; } @@ -416,6 +449,7 @@ void SocketConfig::reset() d_inlineOutOfBandData.reset(); d_timestampIncomingData.reset(); d_timestampOutgoingData.reset(); + d_zeroCopy.reset(); } NTSCFG_INLINE @@ -514,6 +548,12 @@ void SocketConfig::setTimestampOutgoingData(bool value) d_timestampOutgoingData = value; } +NTSCFG_INLINE +void SocketConfig::setZeroCopy(bool value) +{ + d_zeroCopy = value; +} + NTSCFG_INLINE const bdlb::NullableValue& SocketConfig::reuseAddress() const { @@ -612,6 +652,12 @@ const bdlb::NullableValue& SocketConfig::timestampOutgoingData() const return d_timestampOutgoingData; } +NTSCFG_INLINE +const bdlb::NullableValue& SocketConfig::zeroCopy() const +{ + return d_zeroCopy; +} + NTSCFG_INLINE bsl::ostream& operator<<(bsl::ostream& stream, const SocketConfig& object) { @@ -657,6 +703,7 @@ void hashAppend(HASH_ALGORITHM& algorithm, const SocketConfig& value) hashAppend(algorithm, value.inlineOutOfBandData()); hashAppend(algorithm, value.timestampIncomingData()); hashAppend(algorithm, value.timestampOutgoingData()); + hashAppend(algorithm, value.zeroCopy()); } } // close package namespace diff --git a/groups/nts/ntsa/ntsa_socketoption.cpp b/groups/nts/ntsa/ntsa_socketoption.cpp index 114177f0..4672a901 100644 --- a/groups/nts/ntsa/ntsa_socketoption.cpp +++ b/groups/nts/ntsa/ntsa_socketoption.cpp @@ -81,6 +81,14 @@ SocketOption::SocketOption(const SocketOption& other) new (d_timestampOutgoingData.buffer()) bool( other.d_timestampOutgoingData.object()); break; + case ntsa::SocketOptionType::e_RX_TIMESTAMPING: + new (d_timestampIncomingData.buffer()) bool( + other.d_timestampIncomingData.object()); + break; + case ntsa::SocketOptionType::e_ZERO_COPY: + new (d_zeroCopy.buffer()) bool( + other.d_zeroCopy.object()); + break; default: BSLS_ASSERT(d_type == ntsa::SocketOptionType::e_UNDEFINED); } @@ -144,10 +152,18 @@ SocketOption& SocketOption::operator=(const SocketOption& other) new (d_inlineOutOfBandData.buffer()) bool( other.d_inlineOutOfBandData.object()); break; + case ntsa::SocketOptionType::e_RX_TIMESTAMPING: + new (d_timestampIncomingData.buffer()) bool( + other.d_timestampIncomingData.object()); + break; case ntsa::SocketOptionType::e_TX_TIMESTAMPING: new (d_timestampOutgoingData.buffer()) bool( other.d_timestampOutgoingData.object()); break; + case ntsa::SocketOptionType::e_ZERO_COPY: + new (d_zeroCopy.buffer()) bool( + other.d_zeroCopy.object()); + break; default: BSLS_ASSERT(d_type == ntsa::SocketOptionType::e_UNDEFINED); } @@ -605,6 +621,34 @@ bool& SocketOption::makeTimestampOutgoingData(bool value) return d_timestampOutgoingData.object(); } +bool& SocketOption::makeZeroCopy() +{ + if (d_type == ntsa::SocketOptionType::e_ZERO_COPY) { + d_zeroCopy.object() = false; + } + else { + this->reset(); + new (d_zeroCopy.buffer()) bool(); + d_type = ntsa::SocketOptionType::e_ZERO_COPY; + } + + return d_zeroCopy.object(); +} + +bool& SocketOption::makeZeroCopy(bool value) +{ + if (d_type == ntsa::SocketOptionType::e_ZERO_COPY) { + d_zeroCopy.object() = value; + } + else { + this->reset(); + new (d_zeroCopy.buffer()) bool(value); + d_type = ntsa::SocketOptionType::e_ZERO_COPY; + } + + return d_zeroCopy.object(); +} + bool SocketOption::equals(const SocketOption& other) const { if (d_type != other.d_type) { @@ -614,10 +658,48 @@ bool SocketOption::equals(const SocketOption& other) const switch (d_type) { case ntsa::SocketOptionType::e_REUSE_ADDRESS: return d_reuseAddress.object() == other.d_reuseAddress.object(); - // TODO + case ntsa::SocketOptionType::e_KEEP_ALIVE: + return d_keepAlive.object() == other.d_keepAlive.object(); + case ntsa::SocketOptionType::e_CORK: + return d_cork.object() == other.d_cork.object(); + case ntsa::SocketOptionType::e_DELAY_TRANSMISSION: + return d_delayTransmission.object() == + other.d_delayTransmission.object(); + case ntsa::SocketOptionType::e_DELAY_ACKNOWLEDGEMENT: + return d_delayAcknowledgement.object() == + other.d_delayAcknowledgement.object(); + case ntsa::SocketOptionType::e_SEND_BUFFER_SIZE: + return d_sendBufferSize.object() == + other.d_sendBufferSize.object(); + case ntsa::SocketOptionType::e_SEND_BUFFER_LOW_WATERMARK: + return d_sendBufferLowWatermark.object() == + other.d_sendBufferLowWatermark.object(); + case ntsa::SocketOptionType::e_RECEIVE_BUFFER_SIZE: + return d_receiveBufferSize.object() == + other.d_receiveBufferSize.object(); + case ntsa::SocketOptionType::e_RECEIVE_BUFFER_LOW_WATERMARK: + return d_receiveBufferLowWatermark.object() == + other.d_receiveBufferLowWatermark.object(); + case ntsa::SocketOptionType::e_DEBUG: + return d_debug.object() == other.d_debug.object(); case ntsa::SocketOptionType::e_LINGER: return d_linger.object().equals(other.d_linger.object()); - // TODO + case ntsa::SocketOptionType::e_BROADCAST: + return d_broadcast.object() == other.d_broadcast.object(); + case ntsa::SocketOptionType::e_BYPASS_ROUTING: + return d_bypassRouting.object() == other.d_bypassRouting.object(); + case ntsa::SocketOptionType::e_INLINE_OUT_OF_BAND_DATA: + return d_inlineOutOfBandData.object() == + other.d_inlineOutOfBandData.object(); + case ntsa::SocketOptionType::e_RX_TIMESTAMPING: + return d_timestampIncomingData.object() == + other.d_timestampIncomingData.object(); + case ntsa::SocketOptionType::e_TX_TIMESTAMPING: + return d_timestampOutgoingData.object() == + other.d_timestampOutgoingData.object(); + case ntsa::SocketOptionType::e_ZERO_COPY: + return d_zeroCopy.object() == + other.d_zeroCopy.object(); default: return true; } @@ -632,10 +714,47 @@ bool SocketOption::less(const SocketOption& other) const switch (d_type) { case ntsa::SocketOptionType::e_REUSE_ADDRESS: return d_reuseAddress.object() < other.d_reuseAddress.object(); - // TODO + case ntsa::SocketOptionType::e_KEEP_ALIVE: + return d_keepAlive.object() < other.d_keepAlive.object(); + case ntsa::SocketOptionType::e_CORK: + return d_cork.object() < other.d_cork.object(); + case ntsa::SocketOptionType::e_DELAY_TRANSMISSION: + return d_delayTransmission.object() < + other.d_delayTransmission.object(); + case ntsa::SocketOptionType::e_DELAY_ACKNOWLEDGEMENT: + return d_delayAcknowledgement.object() < + other.d_delayAcknowledgement.object(); + case ntsa::SocketOptionType::e_SEND_BUFFER_SIZE: + return d_sendBufferSize.object() < + other.d_sendBufferSize.object(); + case ntsa::SocketOptionType::e_SEND_BUFFER_LOW_WATERMARK: + return d_sendBufferLowWatermark.object() < + other.d_sendBufferLowWatermark.object(); + case ntsa::SocketOptionType::e_RECEIVE_BUFFER_SIZE: + return d_receiveBufferSize.object() < + other.d_receiveBufferSize.object(); + case ntsa::SocketOptionType::e_RECEIVE_BUFFER_LOW_WATERMARK: + return d_receiveBufferLowWatermark.object() < + other.d_receiveBufferLowWatermark.object(); + case ntsa::SocketOptionType::e_DEBUG: + return d_debug.object() < other.d_debug.object(); case ntsa::SocketOptionType::e_LINGER: return d_linger.object().less(other.d_linger.object()); - // TODO + case ntsa::SocketOptionType::e_BROADCAST: + return d_broadcast.object() < other.d_broadcast.object(); + case ntsa::SocketOptionType::e_BYPASS_ROUTING: + return d_bypassRouting.object() < other.d_bypassRouting.object(); + case ntsa::SocketOptionType::e_INLINE_OUT_OF_BAND_DATA: + return d_inlineOutOfBandData.object() < + other.d_inlineOutOfBandData.object(); + case ntsa::SocketOptionType::e_RX_TIMESTAMPING: + return d_timestampIncomingData.object() < + other.d_timestampIncomingData.object(); + case ntsa::SocketOptionType::e_TX_TIMESTAMPING: + return d_timestampOutgoingData.object() < + other.d_timestampOutgoingData.object(); + case ntsa::SocketOptionType::e_ZERO_COPY: + return d_zeroCopy.object() < other.d_zeroCopy.object(); default: return true; } @@ -649,11 +768,54 @@ bsl::ostream& SocketOption::print(bsl::ostream& stream, case ntsa::SocketOptionType::e_REUSE_ADDRESS: stream << d_reuseAddress.object(); break; - // TODO + case ntsa::SocketOptionType::e_KEEP_ALIVE: + stream << d_keepAlive.object(); + break; + case ntsa::SocketOptionType::e_CORK: + stream << d_cork.object(); + break; + case ntsa::SocketOptionType::e_DELAY_TRANSMISSION: + stream << d_delayTransmission.object(); + break; + case ntsa::SocketOptionType::e_DELAY_ACKNOWLEDGEMENT: + stream << d_delayAcknowledgement.object(); + break; + case ntsa::SocketOptionType::e_SEND_BUFFER_SIZE: + stream << d_sendBufferSize.object(); + break; + case ntsa::SocketOptionType::e_SEND_BUFFER_LOW_WATERMARK: + stream << d_sendBufferLowWatermark.object(); + break; + case ntsa::SocketOptionType::e_RECEIVE_BUFFER_SIZE: + stream << d_receiveBufferSize.object(); + break; + case ntsa::SocketOptionType::e_RECEIVE_BUFFER_LOW_WATERMARK: + stream << d_receiveBufferLowWatermark.object(); + break; + case ntsa::SocketOptionType::e_DEBUG: + stream << d_debug.object(); + break; case ntsa::SocketOptionType::e_LINGER: d_linger.object().print(stream, level, spacesPerLevel); break; - // TODO + case ntsa::SocketOptionType::e_BROADCAST: + stream << d_broadcast.object(); + break; + case ntsa::SocketOptionType::e_BYPASS_ROUTING: + stream << d_bypassRouting.object(); + break; + case ntsa::SocketOptionType::e_INLINE_OUT_OF_BAND_DATA: + stream << d_inlineOutOfBandData.object(); + break; + case ntsa::SocketOptionType::e_RX_TIMESTAMPING: + stream << d_timestampIncomingData.object(); + break; + case ntsa::SocketOptionType::e_TX_TIMESTAMPING: + stream << d_timestampOutgoingData.object(); + break; + case ntsa::SocketOptionType::e_ZERO_COPY: + stream << d_zeroCopy.object(); + break; default: BSLS_ASSERT(d_type == ntsa::SocketOptionType::e_UNDEFINED); stream << "UNDEFINED"; diff --git a/groups/nts/ntsa/ntsa_socketoption.h b/groups/nts/ntsa/ntsa_socketoption.h index a74c91ab..c44ab326 100644 --- a/groups/nts/ntsa/ntsa_socketoption.h +++ b/groups/nts/ntsa/ntsa_socketoption.h @@ -35,9 +35,88 @@ namespace ntsa { /// Provide a union of socket options. /// /// @details -/// Provide a value-semantic type that represents a discriminated -/// union of socket options. -// +/// Provide a value-semantic type that represents a discriminated union of +/// socket options. +/// +/// @par Attributes +/// This class is composed of the following attributes. +/// +/// @li @b reuseAddress: +/// The flag that indicates the operating system should allow the user to +/// rebind a socket to reuse local addresses. +/// +/// @li @b keepAlive: +/// That flag that indicates the operating system implementation should +/// periodically emit transport-level "keep-alive" packets. +/// +/// @li @b cork: +/// The flag that indicates that successive writes should be coalesced into the +/// largest packets that can be formed. When set to true, this option indicates +/// the user favors better network efficiency at the expense of worse latency. +/// When set to false, this option indicates the user favors better latency at +/// the expense of worse network efficiency. +/// +/// @li @b delayTransmission: +/// The flag that indicates that successive writes should be coalesced into +/// larger packets that would otherwise form. When set to true, this option +/// indicates the user favors better network efficiency at the expense of worse +/// latency. When set to false, this option indicates the user favors better +/// latency at the expense of worse network efficiency. +/// +/// @li @b delayAcknowledgement: +/// The flag that indicates acknowledgement of successively-received packets +/// should be coalesced. When set to true, this option indicates the user +/// favors better network efficiency at the expense of worse latency. When set +/// to false, this option indicates the user favors better latency at the +/// expense of worse network efficiency. +/// +/// @li @b sendBufferSize: +/// The maximum size of each socket send buffer. On some platforms, this +/// options may serve simply as a hint. +/// +/// @li @b sendBufferLowWatermark: +/// The amount of available capacity that must exist in the socket send buffer +/// for the operating system to indicate the socket is writable. +/// +/// @li @b receiveBufferSize: +/// The maximum size of each socket receive buffer. On some platforms, this +/// options may serve simply as a hint. +/// +/// @li @b receiveBufferLowWatermark: +/// The amount of available data that must exist in the socket receive buffer +/// for the operating system to indicate the socket is readable. +/// +/// @li @b debug: +/// This flag indicates that each socket should be put into debug mode in the +/// operating system. The support and meaning of this option is +/// platform-specific. +/// +/// @li @b linger: +/// The options that control whether the operating system should gracefully +/// attempt to transmit any data remaining in the socket send buffer before +/// closing the connection. +/// +/// @li @b broadcast: +/// The flag that indicates the socket supports sending to a broadcast address. +/// +/// @li @b bypassRouting: +/// The flag that indicates that normal routing rules are not used, the route +/// is based upon the destination address only. +/// +/// @li @b inlineOutOfBandData: +/// The flag that indicates out-of-band data should be placed into the normal +/// data input queue. +/// +/// @li @b timestampIncomingData: +/// The flag that indicates timestamps should be generated for outgoing data. +/// +/// @li @b timestampOutgoingData: +/// The flag that indicates timestamps should be generated for incoming data. +/// +/// @li @b zeroCopy: +/// The flag that indicates each send operation can request copy avoidance when +/// enqueing data to the socket send buffer. +/// /// @par Thread Safety /// This class is not thread safe. /// @@ -61,6 +140,7 @@ class SocketOption bsls::ObjectBuffer d_inlineOutOfBandData; bsls::ObjectBuffer d_timestampIncomingData; bsls::ObjectBuffer d_timestampOutgoingData; + bsls::ObjectBuffer d_zeroCopy; }; ntsa::SocketOptionType::Value d_type; @@ -76,97 +156,88 @@ class SocketOption /// Destroy this object. ~SocketOption(); - /// Assign the value of the specified 'other' object to this object. - /// Return a reference to this modifiable object. + /// Assign the value of the specified 'other' object to this object. Return + /// a reference to this modifiable object. SocketOption& operator=(const SocketOption& other); - /// Reset the value of this object to its value upon default - /// construction. + /// Reset the value of this object to its value upon default construction. void reset(); /// Select the "reuseAddress" representation. Return a reference to the /// modifiable representation. bool& makeReuseAddress(); - /// Select the "reuseAddress" representation initially having the - /// specified 'value'. Return a reference to the modifiable - /// representation. + /// Select the "reuseAddress" representation initially having the specified + /// 'value'. Return a reference to the modifiable representation. bool& makeReuseAddress(bool value); /// Select the "keepAlive" representation. Return a reference to the /// modifiable representation. bool& makeKeepAlive(); - /// Select the "keepAlive" representation initially having the - /// specified 'value'. Return a reference to the modifiable - /// representation. + /// Select the "keepAlive" representation initially having the specified + /// 'value'. Return a reference to the modifiable representation. bool& makeKeepAlive(bool value); - /// Select the "cork" representation. Return a reference to the - /// modifiable representation. + /// Select the "cork" representation. Return a reference to the modifiable + /// representation. bool& makeCork(); - /// Select the "cork" representation initially having the - /// specified 'value'. Return a reference to the modifiable - /// representation. + /// Select the "cork" representation initially having the specified + /// 'value'. Return a reference to the modifiable representation. bool& makeCork(bool value); - /// Select the "delayTransmission" representation. Return a reference - /// to the modifiable representation. + /// Select the "delayTransmission" representation. Return a reference to + /// the modifiable representation. bool& makeDelayTransmission(); /// Select the "delayTransmission" representation initially having the - /// specified 'value'. Return a reference to the modifiable - /// representation. + /// specified 'value'. Return a reference to the modifiable representation. bool& makeDelayTransmission(bool value); - /// Select the "delayAcknowledgement" representation. Return a reference - /// to the modifiable representation. + /// Select the "delayAcknowledgement" representation. Return a reference to + /// the modifiable representation. bool& makeDelayAcknowledgement(); - /// Select the "delayAcknowledgement" representation initially having - /// the specified 'value'. Return a reference to the modifiable - /// representation. + /// Select the "delayAcknowledgement" representation initially having the + /// specified 'value'. Return a reference to the modifiable representation. bool& makeDelayAcknowledgement(bool value); - /// Select the "sendBufferSize" representation. Return a reference - /// to the modifiable representation. + /// Select the "sendBufferSize" representation. Return a reference to the + /// modifiable representation. bsl::size_t& makeSendBufferSize(); - /// Select the "sendBufferSize" representation initially having - /// the specified 'value'. Return a reference to the modifiable - /// representation. + /// Select the "sendBufferSize" representation initially having the + /// specified 'value'. Return a reference to the modifiable representation. bsl::size_t& makeSendBufferSize(bsl::size_t value); - /// Select the "sendBufferLowWatermark" representation. Return a - /// reference to the modifiable representation. + /// Select the "sendBufferLowWatermark" representation. Return a reference + /// to the modifiable representation. bsl::size_t& makeSendBufferLowWatermark(); - /// Select the "sendBufferLowWatermark" representation initially having - /// the specified 'value'. Return a reference to the modifiable - /// representation. + /// Select the "sendBufferLowWatermark" representation initially having the + /// specified 'value'. Return a reference to the modifiable representation. bsl::size_t& makeSendBufferLowWatermark(bsl::size_t value); - /// Select the "receiveBufferSize" representation. Return a reference - /// to the modifiable representation. + /// Select the "receiveBufferSize" representation. Return a reference to + /// the modifiable representation. bsl::size_t& makeReceiveBufferSize(); - /// Select the "receiveBufferSize" representation initially having - /// the specified 'value'. Return a reference to the modifiable - /// representation. + /// Select the "receiveBufferSize" representation initially having the + /// specified 'value'. Return a reference to the modifiable representation. bsl::size_t& makeReceiveBufferSize(bsl::size_t value); /// Select the "receiveBufferLowWatermark" representation. Return a /// reference to the modifiable representation. bsl::size_t& makeReceiveBufferLowWatermark(); - /// Select the "receiveBufferLowWatermark" representation initially - /// having the specified 'value'. Return a reference to the modifiable + /// Select the "receiveBufferLowWatermark" representation initially having + /// the specified 'value'. Return a reference to the modifiable /// representation. bsl::size_t& makeReceiveBufferLowWatermark(bsl::size_t value); - /// Select the "debug" representation. Return a reference to the - /// modifiable representation. + /// Select the "debug" representation. Return a reference to the modifiable + /// representation. bool& makeDebug(); /// Select the "debug" representation initially having the specified @@ -194,47 +265,51 @@ class SocketOption bool& makeBypassRouting(); /// Select the "bypassRouting" representation initially having the - /// specified 'value'. Return a reference to the modifiable - /// representation. + /// specified 'value'. Return a reference to the modifiable representation. bool& makeBypassRouting(bool value); - /// Select the "inlineOutOfBandData" representation. Return a reference - /// to the modifiable representation. + /// Select the "inlineOutOfBandData" representation. Return a reference to + /// the modifiable representation. bool& makeInlineOutOfBandData(); /// Select the "inlineOutOfBandData" representation initially having the - /// specified 'value'. Return a reference to the modifiable - /// representation. + /// specified 'value'. Return a reference to the modifiable representation. bool& makeInlineOutOfBandData(bool value); - /// Select the "timestampIncomingData" representation. Return a - /// reference to the modifiable representation. + /// Select the "timestampIncomingData" representation. Return a reference + /// to the modifiable representation. bool& makeTimestampIncomingData(); - /// Select the "timestampIncomingData" representation initially having - /// the specified 'value'. Return a reference to the modifiable - /// representation + /// Select the "timestampIncomingData" representation initially having the + /// specified 'value'. Return a reference to the modifiable representation. bool& makeTimestampIncomingData(bool value); /// Select the "timestampOutgoingData" representation. Return a reference /// to the modifiable representation. bool& makeTimestampOutgoingData(); - // Select the "timestampOutgoingData" representation initially having the - // specified 'value'. Return a reference to the modifiable - // representation. + /// Select the "timestampOutgoingData" representation initially having the + /// specified 'value'. Return a reference to the modifiable representation. bool& makeTimestampOutgoingData(bool value); - /// Return a reference to the modifiable "reuseAddress" representation. - /// The behavior is undefined unless 'isReuseAddress()' is true. + /// Select the "zeroCopy" representation. Return a reference to the + /// modifiable representation. + bool& makeZeroCopy(); + + /// Select the "zeroCopy" representation initially having the specified + /// 'value'. Return a reference to the modifiable representation. + bool& makeZeroCopy(bool value); + + /// Return a reference to the modifiable "reuseAddress" representation. The + /// behavior is undefined unless 'isReuseAddress()' is true. bool& reuseAddress(); - /// Return a reference to the modifiable "keepAlive" representation. - /// The behavior is undefined unless 'isKeepAlive()' is true. + /// Return a reference to the modifiable "keepAlive" representation. The + /// behavior is undefined unless 'isKeepAlive()' is true. bool& keepAlive(); - /// Return a reference to the modifiable "cork" representation. - /// The behavior is undefined unless 'isCork()' is true. + /// Return a reference to the modifiable "cork" representation. The + /// behavior is undefined unless 'isCork()' is true. bool& cork(); /// Return a reference to the modifiable "delayTransmission" @@ -247,9 +322,8 @@ class SocketOption /// 'isDelayAcknowledgement()' is true. bool& delayAcknowledgement(); - /// Return a reference to the modifiable "sendBufferSize" - /// representation. The behavior is undefined unless - /// 'isSendBufferSize()' is true. + /// Return a reference to the modifiable "sendBufferSize" representation. + /// The behavior is undefined unless 'isSendBufferSize()' is true. bsl::size_t& sendBufferSize(); /// Return a reference to the modifiable "sendBufferLowWatermark" @@ -267,16 +341,16 @@ class SocketOption /// 'isReceiveBufferLowWatermark()' is true. bsl::size_t& receiveBufferLowWatermark(); - /// Return a reference to the modifiable "debug" representation. - /// The behavior is undefined unless 'isDebug()' is true. + /// Return a reference to the modifiable "debug" representation. The + /// behavior is undefined unless 'isDebug()' is true. bool& debug(); - /// Return a reference to the modifiable "linger" representation. - /// The behavior is undefined unless 'isLinger()' is true. + /// Return a reference to the modifiable "linger" representation. The + /// behavior is undefined unless 'isLinger()' is true. ntsa::Linger& linger(); - /// Return a reference to the modifiable "broadcast" representation. - /// The behavior is undefined unless 'isBroadcast()' is true. + /// Return a reference to the modifiable "broadcast" representation. The + /// behavior is undefined unless 'isBroadcast()' is true. bool& broadcast(); /// Return a reference to the modifiable "bypassRouting" representation. @@ -298,12 +372,16 @@ class SocketOption /// 'isTimestampOutOutgoingData()' is true. bool& timestampOutgoingData(); - /// Return the non-modifiable "reuseAddress" representation. The - /// behavior is undefined unless 'isReuseAddress()' is true. + /// Return a reference to the modifiable "zeroCopy" representation. The + /// behavior is undefined unless 'isZeroCopy()' is true. + bool& zeroCopy(); + + /// Return the non-modifiable "reuseAddress" representation. The behavior + /// is undefined unless 'isReuseAddress()' is true. bool reuseAddress() const; - /// Return the non-modifiable "keepAlive" representation. The behavior - /// is undefined unless 'isKeepAlive()' is true. + /// Return the non-modifiable "keepAlive" representation. The behavior is + /// undefined unless 'isKeepAlive()' is true. bool keepAlive() const; /// Return the non-modifiable "cork" representation. The behavior is @@ -318,62 +396,64 @@ class SocketOption /// behavior is undefined unless 'isDelayAcknowledgement()' is true. bool delayAcknowledgement() const; - /// Return the non-modifiable "sendBufferSize" representation. The - /// behavior is undefined unless 'isSendBufferSize()' is true. + /// Return the non-modifiable "sendBufferSize" representation. The behavior + /// is undefined unless 'isSendBufferSize()' is true. bsl::size_t sendBufferSize() const; - /// Return the non-modifiable "sendBufferLowWatermark" representation. - /// The behavior is undefined unless 'isSendBufferLowWatermark()' is - /// true. + /// Return the non-modifiable "sendBufferLowWatermark" representation. The + /// behavior is undefined unless 'isSendBufferLowWatermark()' is true. bsl::size_t sendBufferLowWatermark() const; /// Return the non-modifiable "receiveBufferSize" representation. The /// behavior is undefined unless 'isReceiveBufferSize()' is true. bsl::size_t receiveBufferSize() const; - /// Return the non-modifiable "receiveBufferLowWatermark" - /// representation. The behavior is undefined unless - /// 'isReceiveBufferLowWatermark()' is true. + /// Return the non-modifiable "receiveBufferLowWatermark" representation. + /// The behavior is undefined unless 'isReceiveBufferLowWatermark()' is + /// true. bsl::size_t receiveBufferLowWatermark() const; /// Return the non-modifiable "debug" representation. The behavior is /// undefined unless 'isDebug()' is true. bool debug() const; - /// Return a reference to the non-modifiable "linger" representation. - /// The behavior is undefined unless 'isLinger()' is true. + /// Return a reference to the non-modifiable "linger" representation. The + /// behavior is undefined unless 'isLinger()' is true. const ntsa::Linger& linger() const; - /// Return the non-modifiable "broadcast" representation. The behavior - /// is undefined unless 'isBroadcast()' is true. + /// Return the non-modifiable "broadcast" representation. The behavior is + /// undefined unless 'isBroadcast()' is true. bool broadcast() const; - /// Return the non-modifiable "bypassRouting" representation. The - /// behavior is undefined unless 'isBypassRouting()' is true. + /// Return the non-modifiable "bypassRouting" representation. The behavior + /// is undefined unless 'isBypassRouting()' is true. bool bypassRouting() const; /// Return the non-modifiable "inlineOutOfBandData" representation. The /// behavior is undefined unless 'isInlineOutOfBandData()' is true. bool inlineOutOfBandData() const; - /// Return the non-modifiable "timestampIncomingData" representation. - /// The behavior is undefined unless 'isTimestampIncomingData()' is - /// true. + /// Return the non-modifiable "timestampIncomingData" representation. The + /// behavior is undefined unless 'isTimestampIncomingData()' is true. bool timestampIncomingData() const; /// Return the non-modifiable "timestampOutgoingData" representation. The /// behavior is undefined unless 'isTimestampOutgoingData()' is true. bool timestampOutgoingData() const; + /// Return the non-modifiable "zeroCopy" representation. The behavior is + /// undefined unless 'isZeroCopy()' is true. + bool zeroCopy() const; + /// Return the type of the option representation. enum ntsa::SocketOptionType::Value type() const; - /// Return true if the option representation is undefined, otherwise - /// return false. + /// Return true if the option representation is undefined, otherwise return + /// false. bool isUndefined() const; - /// Return true if the "reuseAddress" representation is currently - /// selected, otherwise return false. + /// Return true if the "reuseAddress" representation is currently selected, + /// otherwise return false. bool isReuseAddress() const; /// Return true if the "keepAlive" representation is currently selected, @@ -396,8 +476,8 @@ class SocketOption /// selected, otherwise return false. bool isSendBufferSize() const; - /// Return true if the "sendBufferLowWatermark" representation is - /// currently selected, otherwise return false. + /// Return true if the "sendBufferLowWatermark" representation is currently + /// selected, otherwise return false. bool isSendBufferLowWatermark() const; /// Return true if the "receiveBufferSize" representation is currently @@ -428,45 +508,48 @@ class SocketOption /// selected, otherwise return false. bool isInlineOutOfBandData() const; - /// Return true if the "timestampIncomingData" representation is - /// currently selected, otherwise return false. + /// Return true if the "timestampIncomingData" representation is currently + /// selected, otherwise return false. bool isTimestampIncomingData() const; - /// Return true if the "timestampOutgoingData" representation is - /// currently selected, otherwise return false. + /// Return true if the "timestampOutgoingData" representation is currently + /// selected, otherwise return false. bool isTimestampOutgoingData() const; - /// Return true if this object has the same value as the specified - /// 'other' object, otherwise return false. + /// Return true if the "zeroCopy" representation is currently selected, + /// otherwise return false. + bool isZeroCopy() const; + + /// Return true if this object has the same value as the specified 'other' + /// object, otherwise return false. bool equals(const SocketOption& other) const; - /// Return true if the value of this object is less than the value of - /// the specified 'other' object, otherwise return false. + /// Return true if the value of this object is less than the value of the + /// specified 'other' object, otherwise return false. bool less(const SocketOption& other) const; - /// Format this object to the specified output 'stream' at the - /// optionally specified indentation 'level' and return a reference to - /// the modifiable 'stream'. If 'level' is specified, optionally - /// specify 'spacesPerLevel', the number of spaces per indentation level - /// for this and all of its nested objects. Each line is indented by - /// the absolute value of 'level * spacesPerLevel'. If 'level' is - /// negative, suppress indentation of the first line. If - /// 'spacesPerLevel' is negative, suppress line breaks and format the - /// entire output on one line. If 'stream' is initially invalid, this - /// operation has no effect. Note that a trailing newline is provided - /// in multiline mode only. + /// Format this object to the specified output 'stream' at the optionally + /// specified indentation 'level' and return a reference to the modifiable + /// 'stream'. If 'level' is specified, optionally specify + /// 'spacesPerLevel', the number of spaces per indentation level for this + /// and all of its nested objects. Each line is indented by the absolute + /// value of 'level * spacesPerLevel'. If 'level' is negative, suppress + /// indentation of the first line. If 'spacesPerLevel' is negative, + /// suppress line breaks and format the entire output on one line. If + /// 'stream' is initially invalid, this operation has no effect. Note that + /// a trailing newline is provided in multiline mode only. bsl::ostream& print(bsl::ostream& stream, int level = 0, int spacesPerLevel = 4) const; - /// Defines the traits of this type. These traits can be used to select, - /// at compile-time, the most efficient algorithm to manipulate objects - /// of this type. + /// Defines the traits of this type. These traits can be used to select, at + /// compile-time, the most efficient algorithm to manipulate objects of + /// this type. NTSCFG_DECLARE_NESTED_BITWISE_MOVABLE_TRAITS(SocketOption); }; -/// Write the specified 'object' to the specified 'stream'. Return a -/// modifiable reference to the 'stream'. +/// Write the specified 'object' to the specified 'stream'. Return a modifiable +/// reference to the 'stream'. /// /// @related ntsa::SocketOption bsl::ostream& operator<<(bsl::ostream& stream, const SocketOption& object); @@ -483,8 +566,8 @@ bool operator==(const SocketOption& lhs, const SocketOption& rhs); /// @related ntsa::SocketOption bool operator!=(const SocketOption& lhs, const SocketOption& rhs); -/// Contribute the values of the salient attributes of the specified 'value' -/// to the specified hash 'algorithm'. +/// Contribute the values of the salient attributes of the specified 'value' to +/// the specified hash 'algorithm'. /// /// @related ntsa::SocketOption template @@ -620,6 +703,13 @@ bool& SocketOption::timestampOutgoingData() return d_timestampOutgoingData.object(); } +NTSCFG_INLINE +bool& SocketOption::zeroCopy() +{ + BSLS_ASSERT(d_type == ntsa::SocketOptionType::e_ZERO_COPY); + return d_zeroCopy.object(); +} + NTSCFG_INLINE bool SocketOption::reuseAddress() const { @@ -733,6 +823,13 @@ bool SocketOption::timestampOutgoingData() const return d_timestampOutgoingData.object(); } +NTSCFG_INLINE +bool SocketOption::zeroCopy() const +{ + BSLS_ASSERT(d_type == ntsa::SocketOptionType::e_ZERO_COPY); + return d_zeroCopy.object(); +} + NTSCFG_INLINE ntsa::SocketOptionType::Value SocketOption::type() const { @@ -841,6 +938,12 @@ bool SocketOption::isTimestampOutgoingData() const return (d_type == ntsa::SocketOptionType::e_TX_TIMESTAMPING); } +NTSCFG_INLINE +bool SocketOption::isZeroCopy() const +{ + return (d_type == ntsa::SocketOptionType::e_ZERO_COPY); +} + NTSCFG_INLINE bsl::ostream& operator<<(bsl::ostream& stream, const SocketOption& object) { @@ -918,6 +1021,9 @@ void hashAppend(HASH_ALGORITHM& algorithm, const SocketOption& value) else if (value.isTimestampOutgoingData()) { hashAppend(algorithm, value.timestampOutgoingData()); } + else if (value.isZeroCopy()) { + hashAppend(algorithm, value.zeroCopy()); + } } } // close package namespace diff --git a/groups/nts/ntsa/ntsa_socketoption.t.cpp b/groups/nts/ntsa/ntsa_socketoption.t.cpp index f849c83e..b6591a4e 100644 --- a/groups/nts/ntsa/ntsa_socketoption.t.cpp +++ b/groups/nts/ntsa/ntsa_socketoption.t.cpp @@ -83,9 +83,35 @@ NTSCFG_TEST_CASE(2) NTSCFG_TEST_FALSE(so.isTimestampOutgoingData()); } +NTSCFG_TEST_CASE(3) +{ + // Concern: test allowMsgZeroCopy option + + ntsa::SocketOption so; + NTSCFG_TEST_FALSE(so.isZeroCopy()); + + so.makeZeroCopy(true); + NTSCFG_TEST_TRUE(so.isZeroCopy()); + + bool& val = so.zeroCopy(); + NTSCFG_TEST_TRUE(val); + val = false; + NTSCFG_TEST_FALSE(so.zeroCopy()); + + val = true; + NTSCFG_TEST_TRUE(so.zeroCopy()); + + so.makeZeroCopy(); + NTSCFG_TEST_FALSE(so.zeroCopy()); + + so.reset(); + NTSCFG_TEST_FALSE(so.isZeroCopy()); +} + NTSCFG_TEST_DRIVER { NTSCFG_TEST_REGISTER(1); NTSCFG_TEST_REGISTER(2); + NTSCFG_TEST_REGISTER(3); } NTSCFG_TEST_DRIVER_END; diff --git a/groups/nts/ntsa/ntsa_socketoptiontype.cpp b/groups/nts/ntsa/ntsa_socketoptiontype.cpp index 9d9b1aec..df7da34b 100644 --- a/groups/nts/ntsa/ntsa_socketoptiontype.cpp +++ b/groups/nts/ntsa/ntsa_socketoptiontype.cpp @@ -43,6 +43,7 @@ int SocketOptionType::fromInt(SocketOptionType::Value* result, int number) case SocketOptionType::e_INLINE_OUT_OF_BAND_DATA: case SocketOptionType::e_RX_TIMESTAMPING: case SocketOptionType::e_TX_TIMESTAMPING: + case SocketOptionType::e_ZERO_COPY: *result = static_cast(number); return 0; default: @@ -122,6 +123,10 @@ int SocketOptionType::fromString(SocketOptionType::Value* result, *result = e_TX_TIMESTAMPING; return 0; } + if (bdlb::String::areEqualCaseless(string, "ZERO_COPY")) { + *result = e_ZERO_COPY; + return 0; + } return -1; } @@ -180,6 +185,9 @@ const char* SocketOptionType::toString(SocketOptionType::Value value) case e_TX_TIMESTAMPING: { return "TX_TIMESTAMPING"; } break; + case e_ZERO_COPY: { + return "ZERO_COPY"; + } break; } BSLS_ASSERT(!"invalid enumerator"); diff --git a/groups/nts/ntsa/ntsa_socketoptiontype.h b/groups/nts/ntsa/ntsa_socketoptiontype.h index 29c33da7..73d877cd 100644 --- a/groups/nts/ntsa/ntsa_socketoptiontype.h +++ b/groups/nts/ntsa/ntsa_socketoptiontype.h @@ -86,11 +86,15 @@ struct SocketOptionType { /// Place out-of-band data into the normal incoming data stream. e_INLINE_OUT_OF_BAND_DATA = 14, - /// This option type allows to enable or disable receive timestamps. + /// Generate timestamps for incoming data. e_RX_TIMESTAMPING = 15, - /// This option type allows to enable or disable transmit timestamps. - e_TX_TIMESTAMPING = 16 + /// Generate timestamps for outgoing data. + e_TX_TIMESTAMPING = 16, + + /// Allow each send operation to request copy avoidance when enqueing + /// data to the socket send buffer. + e_ZERO_COPY = 17 }; /// Return the string representation exactly matching the enumerator diff --git a/groups/nts/ntsa/ntsa_timestamp.h b/groups/nts/ntsa/ntsa_timestamp.h index 76ccca2a..cda01a04 100644 --- a/groups/nts/ntsa/ntsa_timestamp.h +++ b/groups/nts/ntsa/ntsa_timestamp.h @@ -19,30 +19,29 @@ #include BSLS_IDENT("$Id: $") +#include #include - +#include #include #include -#include - namespace BloombergLP { namespace ntsa { -/// Provide a type holding a transmit timestamp. -/// -/// @details -/// Provide a value-semantic type that holds a timestamp. +/// Describe a notification of a timestamp of outgoing data. /// /// @par Attributes -/// This class is composed of the following attributes.. +/// This class is composed of the following attributes. /// /// @li @b type: -/// Type of the timestamp. It describes source of the timestamp. Default value -/// is e_UNDEFINED. +/// The type of the timestamp, indicating where the timestamp was measured in +/// the transmission and acknowledgement process. +/// +/// @li @b id: +/// The data identifier. /// /// @li @b time: -/// The timestamp value. Default value is 0. +/// The timestamp, in absolute time since the Unix epoch. /// /// @par Thread Safety /// This class is not thread safe. @@ -56,54 +55,54 @@ class Timestamp /// Create new timestamp having the default value. Timestamp(); - /// Create new timestamp having the same value as the specified - /// 'original' object. + /// Create new timestamp having the same value as the specified 'original' + /// object. Timestamp(const Timestamp& original); /// Destroy this object. ~Timestamp(); - /// Assign the value of the specified 'other' object to this object. - /// Return a reference to this modifiable object. + /// Assign the value of the specified 'other' object to this object. Return + /// a reference to this modifiable object. Timestamp& operator=(const Timestamp& other); - /// Set timestamp type to the specifued 'value'. + /// Set the timestamp type to the specified 'value'. void setType(ntsa::TimestampType::Value value); - /// Set id of the timestamp to the specified 'value'. + /// Set the identifier of the data for which the timestamp applies to the + /// specified 'value'. void setId(bsl::uint32_t value); - /// Set timestamp time to the specified 'value'. + /// Set the timestamp time to the specified 'value'. void setTime(const bsls::TimeInterval& value); - /// Return type of the timestamp. + /// Return the type of the timestamp. ntsa::TimestampType::Value type() const; - /// Get id of the timestamp. + /// Return the identifier of the data to which the timestamp applies. bsl::uint32_t id() const; - /// Return time of the timestamp. + /// Return time of the timestamp, in absolute time since the Unix epoch. const bsls::TimeInterval& time() const; - /// Return true if this object has the same value as the specified - /// 'other' object, otherwise return false. + /// Return true if this object has the same value as the specified 'other' + /// object, otherwise return false. bool equals(const Timestamp& other) const; - /// Return true if the value of this object is less than the value of - /// the specified 'other' object, otherwise return false. + /// Return true if the value of this object is less than the value of the + /// specified 'other' object, otherwise return false. bool less(const Timestamp& other) const; - /// Format this object to the specified output 'stream' at the - /// optionally specified indentation 'level' and return a reference to - /// the modifiable 'stream'. If 'level' is specified, optionally - /// specify 'spacesPerLevel', the number of spaces per indentation level - /// for this and all of its nested objects. Each line is indented by - /// the absolute value of 'level * spacesPerLevel'. If 'level' is - /// negative, suppress indentation of the first line. If - /// 'spacesPerLevel' is negative, suppress line breaks and format the - /// entire output on one line. If 'stream' is initially invalid, this - /// operation has no effect. Note that a trailing newline is provided - /// in multiline mode only. + /// Format this object to the specified output 'stream' at the optionally + /// specified indentation 'level' and return a reference to the modifiable + /// 'stream'. If 'level' is specified, optionally specify + /// 'spacesPerLevel', the number of spaces per indentation level for this + /// and all of its nested objects. Each line is indented by the absolute + /// value of 'level * spacesPerLevel'. If 'level' is negative, suppress + /// indentation of the first line. If 'spacesPerLevel' is negative, + /// suppress line breaks and format the entire output on one line. If + /// 'stream' is initially invalid, this operation has no effect. Note that + /// a trailing newline is provided in multiline mode only. bsl::ostream& print(bsl::ostream& stream, int level = 0, int spacesPerLevel = 4) const; diff --git a/groups/nts/ntsa/ntsa_timestamptype.h b/groups/nts/ntsa/ntsa_timestamptype.h index 9fd9c305..21624072 100644 --- a/groups/nts/ntsa/ntsa_timestamptype.h +++ b/groups/nts/ntsa/ntsa_timestamptype.h @@ -25,7 +25,7 @@ BSLS_IDENT("$Id: $") namespace BloombergLP { namespace ntsa { -/// Provide an enumeration of the timestamp types. +/// Provide an enumeration of the outgoing timestamp types. /// /// @par Thread Safety /// This struct is thread safe. @@ -33,11 +33,27 @@ namespace ntsa { /// @ingroup module_ntsa_identity struct TimestampType { public: - /// Provide an enumeration of the endpoint types. + /// Provide an enumeration of the timestamp types. enum Value { - e_UNDEFINED = 0, + /// The timestamp type is undefined. + e_UNDEFINED = 0, + + /// The timestamp measured at the time when the data enters the + /// packet scheduler. The delta between such a timestamp and the time + /// immediately before the data is enqueued to the send buffer is the + /// time spent processing the data required by transport protocol. e_SCHEDULED = 1, - e_SENT = 2, + + /// The timestamp measured at the time when the data leaves the + /// operating system and is enqueue in the network device for + /// transmission. The delta between such a timestamp and the scheduled + /// timestamp is the time spending processing the data independant of + /// the transport protocol. + e_SENT = 2, + + /// The timestamp measured at the time when the ackowledgement of the + /// outgoing data has been received from the peer, for positive + /// acknowledgement transport protocols such as TCP. e_ACKNOWLEDGED = 3 }; diff --git a/groups/nts/ntsa/ntsa_zerocopy.cpp b/groups/nts/ntsa/ntsa_zerocopy.cpp new file mode 100644 index 00000000..cc40d299 --- /dev/null +++ b/groups/nts/ntsa/ntsa_zerocopy.cpp @@ -0,0 +1,67 @@ +// Copyright 2020-2023 Bloomberg Finance L.P. +// SPDX-License-Identifier: Apache-2.0 +// +// 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. + +#include + +#include +BSLS_IDENT_RCSID(ntsa_zerocopy_cpp, "$Id$ $CSID$") + +#include + +namespace BloombergLP { +namespace ntsa { + +bool ZeroCopy::equals(const ZeroCopy& other) const +{ + return (d_from == other.d_from && d_to == other.d_to && + d_code == other.d_code); +} + +bool ZeroCopy::less(const ZeroCopy& other) const +{ + if (d_from < other.d_from) { + return true; + } + + if (other.d_from < d_from) { + return false; + } + + if (d_to < other.d_to) { + return true; + } + + if (other.d_to < d_to) { + return false; + } + + return d_code < other.d_code; +} + +bsl::ostream& ZeroCopy::print(bsl::ostream& stream, + int level, + int spacesPerLevel) const +{ + bslim::Printer printer(&stream, level, spacesPerLevel); + printer.start(); + printer.printAttribute("from", d_from); + printer.printAttribute("to", d_to); + printer.printAttribute("code", d_code); + printer.end(); + return stream; +} + +} // close package namespace +} // close enterprise namespace diff --git a/groups/nts/ntsa/ntsa_zerocopy.h b/groups/nts/ntsa/ntsa_zerocopy.h new file mode 100644 index 00000000..3f4afc45 --- /dev/null +++ b/groups/nts/ntsa/ntsa_zerocopy.h @@ -0,0 +1,265 @@ +// Copyright 2020-2023 Bloomberg Finance L.P. +// SPDX-License-Identifier: Apache-2.0 +// +// 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 INCLUDED_NTSA_ZEROCOPY +#define INCLUDED_NTSA_ZEROCOPY + +#include +BSLS_IDENT("$Id: $") + +#include +#include +#include +#include + +namespace BloombergLP { +namespace ntsa { + +/// Describe a notification for the completion of one or more send operations +/// with zero-copy semantics. +/// +/// @par Attributes +/// This class is composed of the following attributes: +/// +/// @li @b from: +/// The identifier of the first zero-copy send that completed, inclusive. +/// +/// @li @b to: +/// The identifier of the last zero-copy send that completed, inclusive. +/// +/// @li @b code: +/// The code indicating whether the copy was avoided or was performed. +/// +/// @par Thread Safety +/// This class is not thread safe. +class ZeroCopy +{ + bsl::uint32_t d_from; + bsl::uint32_t d_to; + bsl::uint8_t d_code; + + public: + /// Create a new instance having the default value. + ZeroCopy(); + + /// Create a new instance using the specified 'from', the specified 'to' + /// and the specified 'code' as initial values. + ZeroCopy(bsl::uint32_t from, bsl::uint32_t to, bsl::uint8_t code); + + /// Create new object having the same value as the specified 'original' + /// object. + ZeroCopy(const ZeroCopy& original); + + /// Destroy this object. + ~ZeroCopy(); + + /// Assign the value of the specified 'other' object to this object. Return + /// a reference to this modifiable object. + ZeroCopy& operator=(const ZeroCopy& other); + + /// Set the identifier of the first zero-copy send that completed, + /// inclusive, to the specified 'value'. + void setFrom(bsl::uint32_t value); + + /// Set the identifier of the last zero-copy send that completed, + /// inclusive, to the specified 'value'. + void setTo(bsl::uint32_t value); + + /// Set the code indicating whether the copy was avoided or was performed + /// to the specified 'value'. + void setCode(bsl::uint8_t value); + + /// Return the identifier of the first zero-copy send that completed, + /// inclusive. + BSLS_ANNOTATION_NODISCARD bsl::uint32_t from() const; + + /// Return the identifier of the last zero-copy send that completed, + /// inclusive. + BSLS_ANNOTATION_NODISCARD bsl::uint32_t to() const; + + /// Return the code indicating whether the copy was avoided or was + /// performed. + BSLS_ANNOTATION_NODISCARD bsl::uint8_t code() const; + + /// Return true if this object has the same value as the specified + /// 'other' object, otherwise return false. + BSLS_ANNOTATION_NODISCARD bool equals(const ZeroCopy& other) const; + + /// Return true if the value of this object is less than the value of + /// the specified 'other' object, otherwise return false. + BSLS_ANNOTATION_NODISCARD bool less(const ZeroCopy& other) const; + + /// Format this object to the specified output 'stream' at the optionally + /// specified indentation 'level' and return a reference to the modifiable + /// 'stream'. If 'level' is specified, optionally specify + /// 'spacesPerLevel', the number of spaces per indentation level for this + /// and all of its nested objects. Each line is indented by the absolute + /// value of 'level * spacesPerLevel'. If 'level' is negative, suppress + /// indentation of the first line. If 'spacesPerLevel' is negative, + /// suppress line breaks and format the entire output on one line. If + /// 'stream' is initially invalid, this operation has no effect. Note that + /// a trailing newline is provided in multiline mode only. + bsl::ostream& print(bsl::ostream& stream, + int level = 0, + int spacesPerLevel = 4) const; + + /// Defines the traits of this type. These traits can be used to select, + /// at compile-time, the most efficient algorithm to manipulate objects + /// of this type. + NTSCFG_DECLARE_NESTED_BITWISE_MOVABLE_TRAITS(ZeroCopy); +}; + +/// Write the specified 'object' to the specified 'stream'. Return +/// a modifiable reference to the 'stream'. +/// +/// @related ntsa::ZeroCopy +bsl::ostream& operator<<(bsl::ostream& stream, const ZeroCopy& object); + +/// Return true if the specified 'lhs' has the same value as the specified +/// 'rhs', otherwise return false. +/// +/// @related ntsa::ZeroCopy +bool operator==(const ZeroCopy& lhs, const ZeroCopy& rhs); + +/// Return true if the specified 'lhs' does not have the same value as the +/// specified 'rhs', otherwise return false. +/// +/// @related ntsa::ZeroCopy +bool operator!=(const ZeroCopy& lhs, const ZeroCopy& rhs); + +/// Return true if the value of the specified 'lhs' is less than the value +/// of the specified 'rhs', otherwise return false. +/// +/// @related ntsa::ZeroCopy +bool operator<(const ZeroCopy& lhs, const ZeroCopy& rhs); + +/// Contribute the values of the salient attributes of the specified 'value' +/// to the specified hash 'algorithm'. +/// +/// @related ntsa::ZeroCopy +template +void hashAppend(HASH_ALGORITHM& algorithm, const ZeroCopy& value); + +NTSCFG_INLINE +ZeroCopy::ZeroCopy() +: d_from(0) +, d_to(0) +, d_code(0) +{ +} + +NTSCFG_INLINE +ZeroCopy::ZeroCopy(bsl::uint32_t from, bsl::uint32_t to, bsl::uint8_t code) +: d_from(from) +, d_to(to) +, d_code(code) +{ +} + +NTSCFG_INLINE +ZeroCopy::ZeroCopy(const ZeroCopy& original) +: d_from(original.d_from) +, d_to(original.d_to) +, d_code(original.d_code) +{ +} + +NTSCFG_INLINE +ZeroCopy::~ZeroCopy() +{ +} + +NTSCFG_INLINE +ZeroCopy& ZeroCopy::operator=(const ZeroCopy& other) +{ + d_from = other.d_from; + d_to = other.d_to; + d_code = other.d_code; + return *this; +} + +NTSCFG_INLINE +void ZeroCopy::setFrom(bsl::uint32_t value) +{ + d_from = value; +} + +NTSCFG_INLINE +void ZeroCopy::setTo(bsl::uint32_t value) +{ + d_to = value; +} + +NTSCFG_INLINE +void ZeroCopy::setCode(bsl::uint8_t value) +{ + d_code = value; +} + +NTSCFG_INLINE +bsl::uint32_t ZeroCopy::from() const +{ + return d_from; +} + +NTSCFG_INLINE +bsl::uint32_t ZeroCopy::to() const +{ + return d_to; +} + +NTSCFG_INLINE +bsl::uint8_t ZeroCopy::code() const +{ + return d_code; +} + +NTSCFG_INLINE +bsl::ostream& operator<<(bsl::ostream& stream, const ZeroCopy& object) +{ + return object.print(stream, 0, -1); +} + +NTSCFG_INLINE +bool operator==(const ZeroCopy& lhs, const ZeroCopy& rhs) +{ + return lhs.equals(rhs); +} + +NTSCFG_INLINE +bool operator!=(const ZeroCopy& lhs, const ZeroCopy& rhs) +{ + return !operator==(lhs, rhs); +} + +NTSCFG_INLINE +bool operator<(const ZeroCopy& lhs, const ZeroCopy& rhs) +{ + return lhs.less(rhs); +} + +template +void hashAppend(HASH_ALGORITHM& algorithm, const ZeroCopy& value) +{ + using bslh::hashAppend; + + hashAppend(algorithm, value.from()); + hashAppend(algorithm, value.to()); + hashAppend(algorithm, value.code()); +} + +} // close package namespace +} // close enterprise namespace +#endif diff --git a/groups/nts/ntsa/ntsa_zerocopy.t.cpp b/groups/nts/ntsa/ntsa_zerocopy.t.cpp new file mode 100644 index 00000000..5cd8e4ee --- /dev/null +++ b/groups/nts/ntsa/ntsa_zerocopy.t.cpp @@ -0,0 +1,63 @@ +// Copyright 2020-2023 Bloomberg Finance L.P. +// SPDX-License-Identifier: Apache-2.0 +// +// 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. + +#include + +#include + +using namespace BloombergLP; +using namespace ntsa; + +NTSCFG_TEST_CASE(1) +{ + { + ZeroCopy zc; + NTSCFG_TEST_EQ(zc.from(), 0); + NTSCFG_TEST_EQ(zc.to(), 0); + NTSCFG_TEST_EQ(zc.code(), 0); + } + { + const bsl::uint32_t from = 5; + const bsl::uint32_t to = 15; + const bsl::uint8_t code = 1; + + ZeroCopy zc(from, to, code); + NTSCFG_TEST_EQ(zc.from(), from); + NTSCFG_TEST_EQ(zc.to(), to); + NTSCFG_TEST_EQ(zc.code(), code); + } + { + const bsl::uint32_t from = 10; + const bsl::uint32_t to = 22; + const bsl::uint8_t code = 1; + + ZeroCopy zc; + zc.setFrom(from); + zc.setTo(to); + zc.setCode(code); + NTSCFG_TEST_EQ(zc.from(), from); + NTSCFG_TEST_EQ(zc.to(), to); + NTSCFG_TEST_EQ(zc.code(), code); + + ZeroCopy copy(zc); + NTSCFG_TEST_EQ(copy, zc); + } +} + +NTSCFG_TEST_DRIVER +{ + NTSCFG_TEST_REGISTER(1); +} +NTSCFG_TEST_DRIVER_END; diff --git a/groups/nts/ntsa/package/ntsa.mem b/groups/nts/ntsa/package/ntsa.mem index 77bfd63b..ff4663b3 100644 --- a/groups/nts/ntsa/package/ntsa.mem +++ b/groups/nts/ntsa/package/ntsa.mem @@ -53,3 +53,4 @@ ntsa_transport ntsa_timestamp ntsa_timestamptype ntsa_uri +ntsa_zerocopy diff --git a/groups/nts/ntsb/ntsb_datagramsocket.cpp b/groups/nts/ntsb/ntsb_datagramsocket.cpp index 0d59463d..3b0360dd 100644 --- a/groups/nts/ntsb/ntsb_datagramsocket.cpp +++ b/groups/nts/ntsb/ntsb_datagramsocket.cpp @@ -122,6 +122,12 @@ ntsa::Error DatagramSocket::receive(ntsa::ReceiveContext* context, return ntsu::SocketUtil::receive(context, data, options, d_handle); } +ntsa::Error DatagramSocket::receiveNotifications( + ntsa::NotificationQueue* notifications) +{ + return ntsu::SocketUtil::receiveNotifications(notifications, d_handle); +} + ntsa::Error DatagramSocket::shutdown(ntsa::ShutdownType::Value direction) { return ntsu::SocketUtil::shutdown(direction, d_handle); diff --git a/groups/nts/ntsb/ntsb_datagramsocket.h b/groups/nts/ntsb/ntsb_datagramsocket.h index d00b2cda..1cdcc326 100644 --- a/groups/nts/ntsb/ntsb_datagramsocket.h +++ b/groups/nts/ntsb/ntsb_datagramsocket.h @@ -123,6 +123,12 @@ class DatagramSocket : public ntsi::DatagramSocket const ntsa::ReceiveOptions& options) BSLS_KEYWORD_OVERRIDE; + /// Read data from the socket error queue. Then if the specified + /// 'notifications' is not null parse fetched data to extract control + /// messages into the specified 'notifications'. Return the error. + ntsa::Error receiveNotifications(ntsa::NotificationQueue* notifications) + BSLS_KEYWORD_OVERRIDE; + /// Shutdown the stream socket in the specified 'direction'. Return the /// error. ntsa::Error shutdown(ntsa::ShutdownType::Value direction) diff --git a/groups/nts/ntsb/ntsb_listenersocket.cpp b/groups/nts/ntsb/ntsb_listenersocket.cpp index 9bebec9e..ae1eeefa 100644 --- a/groups/nts/ntsb/ntsb_listenersocket.cpp +++ b/groups/nts/ntsb/ntsb_listenersocket.cpp @@ -147,6 +147,12 @@ ntsa::Error ListenerSocket::accept(bsl::shared_ptr* result, return ntsa::Error(); } +ntsa::Error ListenerSocket::receiveNotifications( + ntsa::NotificationQueue* notifications) +{ + return ntsu::SocketUtil::receiveNotifications(notifications, d_handle); +} + ntsa::Error ListenerSocket::shutdown(ntsa::ShutdownType::Value direction) { return ntsu::SocketUtil::shutdown(direction, d_handle); diff --git a/groups/nts/ntsb/ntsb_listenersocket.h b/groups/nts/ntsb/ntsb_listenersocket.h index 7a4c5a5e..37cf6c76 100644 --- a/groups/nts/ntsb/ntsb_listenersocket.h +++ b/groups/nts/ntsb/ntsb_listenersocket.h @@ -113,6 +113,12 @@ class ListenerSocket : public ntsi::ListenerSocket bslma::Allocator* basicAllocator = 0) BSLS_KEYWORD_OVERRIDE; + /// Read data from the socket error queue. Then if the specified + /// 'notifications' is not null parse fetched data to extract control + /// messages into the specified 'notifications'. Return the error. + ntsa::Error receiveNotifications(ntsa::NotificationQueue* notifications) + BSLS_KEYWORD_OVERRIDE; + /// Shutdown the stream socket in the specified 'direction'. Return the /// error. ntsa::Error shutdown(ntsa::ShutdownType::Value direction) diff --git a/groups/nts/ntsb/ntsb_streamsocket.cpp b/groups/nts/ntsb/ntsb_streamsocket.cpp index cd7077d1..92f5e1d1 100644 --- a/groups/nts/ntsb/ntsb_streamsocket.cpp +++ b/groups/nts/ntsb/ntsb_streamsocket.cpp @@ -122,6 +122,12 @@ ntsa::Error StreamSocket::receive(ntsa::ReceiveContext* context, return ntsu::SocketUtil::receive(context, data, options, d_handle); } +ntsa::Error StreamSocket::receiveNotifications( + ntsa::NotificationQueue* notifications) +{ + return ntsu::SocketUtil::receiveNotifications(notifications, d_handle); +} + ntsa::Error StreamSocket::shutdown(ntsa::ShutdownType::Value direction) { return ntsu::SocketUtil::shutdown(direction, d_handle); diff --git a/groups/nts/ntsb/ntsb_streamsocket.h b/groups/nts/ntsb/ntsb_streamsocket.h index a4e294c5..502b7602 100644 --- a/groups/nts/ntsb/ntsb_streamsocket.h +++ b/groups/nts/ntsb/ntsb_streamsocket.h @@ -117,6 +117,12 @@ class StreamSocket : public ntsi::StreamSocket const ntsa::ReceiveOptions& options) BSLS_KEYWORD_OVERRIDE; + /// Read data from the socket error queue. Then if the specified + /// 'notifications' is not null parse fetched data to extract control + /// messages into the specified 'notifications'. Return the error. + ntsa::Error receiveNotifications(ntsa::NotificationQueue* notifications) + BSLS_KEYWORD_OVERRIDE; + /// Shutdown the stream socket in the specified 'direction'. Return the /// error. ntsa::Error shutdown(ntsa::ShutdownType::Value direction) diff --git a/groups/nts/ntscfg/ntscfg_platform.h b/groups/nts/ntscfg/ntscfg_platform.h index 04233471..36fc582b 100644 --- a/groups/nts/ntscfg/ntscfg_platform.h +++ b/groups/nts/ntscfg/ntscfg_platform.h @@ -217,7 +217,8 @@ struct Platform { static int exit(); /// Return true if the version of the operating system running the current - /// process supports timestamping incoming and outgoing data, otherwise return false. + /// process supports timestamping incoming and outgoing data, otherwise + /// return false. static bool supportsTimestamps(); }; diff --git a/groups/nts/ntsi/ntsi_datagramsocket.cpp b/groups/nts/ntsi/ntsi_datagramsocket.cpp index 6201d688..f3d67ceb 100644 --- a/groups/nts/ntsi/ntsi_datagramsocket.cpp +++ b/groups/nts/ntsi/ntsi_datagramsocket.cpp @@ -94,6 +94,14 @@ ntsa::Error DatagramSocket::receive(ntsa::ReceiveContext* context, return ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED); } +ntsa::Error DatagramSocket::receiveNotifications( + ntsa::NotificationQueue* notifications) +{ + NTSCFG_WARNING_UNUSED(notifications); + + return ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED); +} + ntsa::Error DatagramSocket::shutdown(ntsa::ShutdownType::Value direction) { NTSCFG_WARNING_UNUSED(direction); diff --git a/groups/nts/ntsi/ntsi_datagramsocket.h b/groups/nts/ntsi/ntsi_datagramsocket.h index 20ae2d0c..e984b1f0 100644 --- a/groups/nts/ntsi/ntsi_datagramsocket.h +++ b/groups/nts/ntsi/ntsi_datagramsocket.h @@ -23,6 +23,7 @@ BSLS_IDENT("$Id: $") #include #include #include +#include #include #include #include @@ -502,6 +503,12 @@ class DatagramSocket : public ntsi::Channel bsl::size_t capacity, const ntsa::ReceiveOptions& options); + /// Read data from the socket error queue. Then if the specified + /// 'notifications' is not null parse fetched data to extract control + /// messages into the specified 'notifications'. Return the error. + virtual ntsa::Error receiveNotifications( + ntsa::NotificationQueue* notifications); + /// Shutdown the stream socket in the specified 'direction'. Return the /// error. ntsa::Error shutdown(ntsa::ShutdownType::Value direction) diff --git a/groups/nts/ntsi/ntsi_listenersocket.cpp b/groups/nts/ntsi/ntsi_listenersocket.cpp index a2571a10..17c08194 100644 --- a/groups/nts/ntsi/ntsi_listenersocket.cpp +++ b/groups/nts/ntsi/ntsi_listenersocket.cpp @@ -76,6 +76,14 @@ ntsa::Error ListenerSocket::accept(bsl::shared_ptr* result, return ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED); } +ntsa::Error ListenerSocket::receiveNotifications( + ntsa::NotificationQueue* notifications) +{ + NTSCFG_WARNING_UNUSED(notifications); + + return ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED); +} + ntsa::Error ListenerSocket::shutdown(ntsa::ShutdownType::Value direction) { NTSCFG_WARNING_UNUSED(direction); diff --git a/groups/nts/ntsi/ntsi_listenersocket.h b/groups/nts/ntsi/ntsi_listenersocket.h index cc25beb4..f511d87c 100644 --- a/groups/nts/ntsi/ntsi_listenersocket.h +++ b/groups/nts/ntsi/ntsi_listenersocket.h @@ -21,6 +21,7 @@ BSLS_IDENT("$Id: $") #include #include +#include #include #include #include @@ -147,6 +148,12 @@ class ListenerSocket : public ntsi::Descriptor virtual ntsa::Error accept(bsl::shared_ptr* result, bslma::Allocator* basicAllocator = 0); + /// Read data from the socket error queue. Then if the specified + /// 'notifications' is not null parse fetched data to extract control + /// messages into the specified 'notifications'. Return the error. + virtual ntsa::Error receiveNotifications( + ntsa::NotificationQueue* notifications); + /// Shutdown the stream socket in the specified 'direction'. Return the /// error. virtual ntsa::Error shutdown(ntsa::ShutdownType::Value direction); diff --git a/groups/nts/ntsi/ntsi_streamsocket.cpp b/groups/nts/ntsi/ntsi_streamsocket.cpp index cf8ebdc7..ba04444c 100644 --- a/groups/nts/ntsi/ntsi_streamsocket.cpp +++ b/groups/nts/ntsi/ntsi_streamsocket.cpp @@ -94,6 +94,14 @@ ntsa::Error StreamSocket::receive(ntsa::ReceiveContext* context, return ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED); } +ntsa::Error StreamSocket::receiveNotifications( + ntsa::NotificationQueue* notifications) +{ + NTSCFG_WARNING_UNUSED(notifications); + + return ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED); +} + ntsa::Error StreamSocket::shutdown(ntsa::ShutdownType::Value direction) { NTSCFG_WARNING_UNUSED(direction); diff --git a/groups/nts/ntsi/ntsi_streamsocket.h b/groups/nts/ntsi/ntsi_streamsocket.h index 28b963ec..95796927 100644 --- a/groups/nts/ntsi/ntsi_streamsocket.h +++ b/groups/nts/ntsi/ntsi_streamsocket.h @@ -22,6 +22,7 @@ BSLS_IDENT("$Id: $") #include #include #include +#include #include #include #include @@ -297,6 +298,12 @@ class StreamSocket : public ntsi::Channel bsl::size_t capacity, const ntsa::ReceiveOptions& options); + /// Read data from the socket error queue. Then if the specified + /// 'notifications' is not null parse fetched data to extract control + /// messages into the specified 'notifications'. Return the error. + virtual ntsa::Error receiveNotifications( + ntsa::NotificationQueue* notifications); + /// Shutdown the stream socket in the specified 'direction'. Return the /// error. ntsa::Error shutdown(ntsa::ShutdownType::Value direction) diff --git a/groups/nts/ntsu/ntsu_msgzerocopyutil.t.cpp b/groups/nts/ntsu/ntsu_msgzerocopyutil.t.cpp new file mode 100644 index 00000000..d1bd4c3c --- /dev/null +++ b/groups/nts/ntsu/ntsu_msgzerocopyutil.t.cpp @@ -0,0 +1,28 @@ +// Copyright 2023 Bloomberg Finance L.P. +// SPDX-License-Identifier: Apache-2.0 +// +// 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. + +#include + +using namespace BloombergLP; + +NTSCFG_TEST_CASE(1) +{ +} + +NTSCFG_TEST_DRIVER +{ + NTSCFG_TEST_REGISTER(1); +} +NTSCFG_TEST_DRIVER_END; diff --git a/groups/nts/ntsu/ntsu_socketoptionutil.cpp b/groups/nts/ntsu/ntsu_socketoptionutil.cpp index b8defe18..744d0245 100644 --- a/groups/nts/ntsu/ntsu_socketoptionutil.cpp +++ b/groups/nts/ntsu/ntsu_socketoptionutil.cpp @@ -20,6 +20,7 @@ BSLS_IDENT_RCSID(ntsu_socketoptionutil_cpp, "$Id$ $CSID$") #include #include +#include #include #include @@ -155,6 +156,9 @@ ntsa::Error SocketOptionUtil::setOption(ntsa::Handle socket, socket, option.timestampOutgoingData()); } + else if (option.isZeroCopy()) { + return SocketOptionUtil::setZeroCopy(socket, option.zeroCopy()); + } else { return ntsa::Error(ntsa::Error::e_INVALID); } @@ -295,6 +299,15 @@ ntsa::Error SocketOptionUtil::getOption(ntsa::SocketOption* option, option->makeTimestampIncomingData(value); return ntsa::Error(); } + else if (type == ntsa::SocketOptionType::e_ZERO_COPY) { + bool value = false; + error = SocketOptionUtil::getZeroCopy(&value, socket); + if (error) { + return error; + } + option->makeZeroCopy(value); + return ntsa::Error(); + } else { return ntsa::Error(ntsa::Error::e_INVALID); } @@ -688,6 +701,31 @@ ntsa::Error SocketOptionUtil::setInlineOutOfBandData(ntsa::Handle socket, return ntsa::Error(); } +ntsa::Error SocketOptionUtil::setZeroCopy(ntsa::Handle socket, bool zeroCopy) +{ +#if defined(BSLS_PLATFORM_OS_LINUX) + + int optionValue = static_cast(zeroCopy); + + int rc = setsockopt(socket, + SOL_SOCKET, + ntsu::ZeroCopyUtil::e_SO_ZEROCOPY, + &optionValue, + sizeof(optionValue)); + + if (rc != 0) { + return ntsa::Error(errno); + } + + return ntsa::Error(); +#else + NTSCFG_WARNING_UNUSED(socket); + NTSCFG_WARNING_UNUSED(zeroCopy); + + return ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED); +#endif +} + ntsa::Error SocketOptionUtil::getKeepAlive(bool* keepAlive, ntsa::Handle socket) { @@ -782,7 +820,7 @@ ntsa::Error SocketOptionUtil::getLinger(bool* linger, bsls::TimeInterval* duration, ntsa::Handle socket) { - *linger = false; + *linger = false; *duration = bsls::TimeInterval(); struct linger optionValue = {0, 0}; @@ -1052,6 +1090,41 @@ ntsa::Error SocketOptionUtil::getTimestampIncomingData(bool* timestampFlag, #endif } +ntsa::Error SocketOptionUtil::getZeroCopy(bool* zeroCopyFlag, + ntsa::Handle socket) +{ +#if defined(BSLS_PLATFORM_OS_LINUX) + int optionValue = 0; + socklen_t len = static_cast(sizeof(optionValue)); + + int rc = getsockopt(socket, + SOL_SOCKET, + ntsu::ZeroCopyUtil::e_SO_ZEROCOPY, + &optionValue, + &len); + + if (rc != 0) { + return ntsa::Error(errno); + } + + if (optionValue != 0) { + *zeroCopyFlag = true; + } + else { + *zeroCopyFlag = false; + } + + return ntsa::Error(); + +#else + + NTSCFG_WARNING_UNUSED(zeroCopyFlag); + NTSCFG_WARNING_UNUSED(socket); + return ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED); + +#endif +} + ntsa::Error SocketOptionUtil::getSendBufferRemaining(bsl::size_t* size, ntsa::Handle socket) { @@ -1725,7 +1798,6 @@ ntsa::Error SocketOptionUtil::setReuseAddress(ntsa::Handle socket, return ntsa::Error(); } - ntsa::Error SocketOptionUtil::setTimestampOutgoingData(ntsa::Handle socket, bool timestampFlag) { @@ -1987,7 +2059,7 @@ ntsa::Error SocketOptionUtil::getLinger(bool* linger, bsls::TimeInterval* duration, ntsa::Handle socket) { - *linger = false; + *linger = false; *duration = bsls::TimeInterval(); struct linger optionValue = {}; diff --git a/groups/nts/ntsu/ntsu_socketoptionutil.h b/groups/nts/ntsu/ntsu_socketoptionutil.h index 2f69772f..048ecec3 100644 --- a/groups/nts/ntsu/ntsu_socketoptionutil.h +++ b/groups/nts/ntsu/ntsu_socketoptionutil.h @@ -71,15 +71,6 @@ struct SocketOptionUtil { /// according to the specified 'reuseAddress' flag. Return the error. static ntsa::Error setReuseAddress(ntsa::Handle socket, bool reuseAddress); - /// Set the option for the specified 'socket' that enables or disables - /// application of both software and hardware timestamps for incoming - /// data according to the specified 'timestampIncomingData' flag. Note - /// that if an error is returned then timestamps will never be - /// generated. If there is no error returned then in some cases OS can - /// also refuse to generate timestamps. - static ntsa::Error setTimestampIncomingData(ntsa::Handle socket, - bool timestampIncomingData); - /// Set the option for the specified 'oscket' that controls how the /// operating system will linger its underlying resources after it has /// been closed to the specified 'linger' flag for the specified @@ -129,6 +120,15 @@ struct SocketOptionUtil { static ntsa::Error setInlineOutOfBandData(ntsa::Handle socket, bool inlineFlag); + /// Set the option for the specified 'socket' that enables or disables + /// application of both software and hardware timestamps for incoming + /// data according to the specified 'timestampIncomingData' flag. Note + /// that if an error is returned then timestamps will never be + /// generated. If there is no error returned then in some cases OS can + /// also refuse to generate timestamps. + static ntsa::Error setTimestampIncomingData(ntsa::Handle socket, + bool timestampIncomingData); + /// Set the option for the specified 'socket' that enables or disables /// application of timestamps for outgoing data according to the specified /// 'timestampOutgoingData' flag. Note that if an error is returned then @@ -137,6 +137,11 @@ struct SocketOptionUtil { static ntsa::Error setTimestampOutgoingData(ntsa::Handle socket, bool timestampOutgoingData); + /// Set the option for the specified 'socket' that allows Linux + /// MSG_ZEROCOPY mechanism usage according to the specified 'zeroCopy' + /// flag. Return the error. + static ntsa::Error setZeroCopy(ntsa::Handle socket, bool zeroCopy); + /// Load into the specified 'option' the socket option of the specified /// 'type' for the specified 'socket'. Return the error. static ntsa::Error getOption(ntsa::SocketOption* option, @@ -219,6 +224,12 @@ struct SocketOptionUtil { static ntsa::Error getTimestampIncomingData(bool* timestampFlag, ntsa::Handle socket); + /// Load into the specified 'zeroCopyFlag' the option for the specified + /// 'socket' that indicates if Linux MSG_ZEROCOPY mechanism can be used. + /// Return the error. + static ntsa::Error getZeroCopy(bool* zeroCopyFlag, + ntsa::Handle socket); + /// Load into the specified 'size' the option for the specified 'socket' /// that indicates the amount of space left in the send buffer. Return /// the error. diff --git a/groups/nts/ntsu/ntsu_socketoptionutil.t.cpp b/groups/nts/ntsu/ntsu_socketoptionutil.t.cpp index 3b2d4a31..70e1e659 100644 --- a/groups/nts/ntsu/ntsu_socketoptionutil.t.cpp +++ b/groups/nts/ntsu/ntsu_socketoptionutil.t.cpp @@ -1762,6 +1762,144 @@ NTSCFG_TEST_CASE(8) } } +NTSCFG_TEST_CASE(9) +{ + // test SO_ZEROCOPY + const ntsa::Transport::Value SOCKET_TYPES[] = { + ntsa::Transport::e_TCP_IPV4_STREAM, + ntsa::Transport::e_TCP_IPV6_STREAM, +#if !defined(BSLS_PLATFORM_OS_WINDOWS) + ntsa::Transport::e_LOCAL_STREAM, +#endif + ntsa::Transport::e_UDP_IPV4_DATAGRAM, + ntsa::Transport::e_UDP_IPV6_DATAGRAM, +#if !defined(BSLS_PLATFORM_OS_WINDOWS) + ntsa::Transport::e_LOCAL_DATAGRAM, +#endif + }; + + for (bsl::size_t socketTypeIndex = 0; + socketTypeIndex < sizeof(SOCKET_TYPES) / sizeof(SOCKET_TYPES[0]); + ++socketTypeIndex) + { + ntsa::Transport::Value transport = SOCKET_TYPES[socketTypeIndex]; + + if (transport == ntsa::Transport::e_TCP_IPV4_STREAM || + transport == ntsa::Transport::e_UDP_IPV4_DATAGRAM) + { + if (!ntsu::AdapterUtil::supportsIpv4()) { + continue; + } + } + + if (transport == ntsa::Transport::e_TCP_IPV6_STREAM || + transport == ntsa::Transport::e_UDP_IPV6_DATAGRAM) + { + if (!ntsu::AdapterUtil::supportsIpv6()) { + continue; + } + } + +#if defined(BSLS_PLATFORM_OS_LINUX) + + bool setSupported = false; + bool getSupported = false; + + { + int major, minor, patch, build; + NTSCFG_TEST_ASSERT(ntsscm::Version::systemVersion(&major, + &minor, + &patch, + &build) == 0); + + // Linux kernels versions < 4.14.0 do not support MSG_ZEROCOPY at + // all + // Linux kernels versions < 5.0.0 do not support MSG_ZEROCOPY for + // DGRAM sockets + if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(4, 14, 0)) + { + setSupported = false; + getSupported = false; + } + else if (KERNEL_VERSION(major, minor, patch) < + KERNEL_VERSION(5, 0, 0)) + { + if (transport == ntsa::Transport::e_TCP_IPV4_STREAM || + transport == ntsa::Transport::e_TCP_IPV4_STREAM) + { + setSupported = true; + } + getSupported = true; + } + else { + setSupported = + (transport != ntsa::Transport::e_LOCAL_STREAM) && + (transport != ntsa::Transport::e_LOCAL_DATAGRAM); + getSupported = true; + } + } + +#else + const bool setSupported = false; + const bool getSupported = false; +#endif + + NTSCFG_TEST_LOG_WARN << "Testing " << transport << NTSCFG_TEST_LOG_END; + + ntsa::Error error; + + // Create the socket. + + ntsa::Handle socket; + error = ntsu::SocketUtil::create(&socket, transport); + NTSCFG_TEST_OK(error); + + if (setSupported && getSupported) { + bool zeroCopy = true; + error = + ntsu::SocketOptionUtil::getZeroCopy(&zeroCopy, socket); + NTSCFG_TEST_OK(error); + NTSCFG_TEST_FALSE(zeroCopy); + + error = ntsu::SocketOptionUtil::setZeroCopy(socket, true); + NTSCFG_TEST_OK(error); + + zeroCopy = false; + error = + ntsu::SocketOptionUtil::getZeroCopy(&zeroCopy, socket); + NTSCFG_TEST_OK(error); + NTSCFG_TEST_TRUE(zeroCopy); + + error = ntsu::SocketOptionUtil::setZeroCopy(socket, false); + NTSCFG_TEST_OK(error); + + zeroCopy = true; + error = + ntsu::SocketOptionUtil::getZeroCopy(&zeroCopy, socket); + NTSCFG_TEST_OK(error); + NTSCFG_TEST_FALSE(zeroCopy); + } + else if (setSupported == false && getSupported == true) { + bool zeroCopy = true; + error = + ntsu::SocketOptionUtil::getZeroCopy(&zeroCopy, socket); + NTSCFG_TEST_OK(error); + NTSCFG_TEST_FALSE(zeroCopy); + + error = ntsu::SocketOptionUtil::setZeroCopy(socket, false); + NTSCFG_TEST_TRUE(error); + } + else if (setSupported == false && getSupported == false) { + bool zeroCopy = true; + error = + ntsu::SocketOptionUtil::getZeroCopy(&zeroCopy, socket); + NTSCFG_TEST_ERROR(error, + ntsa::Error(ntsa::Error::e_NOT_IMPLEMENTED)); + NTSCFG_TEST_TRUE(zeroCopy); + } + } +} + NTSCFG_TEST_DRIVER { NTSCFG_TEST_REGISTER(1); @@ -1772,5 +1910,6 @@ NTSCFG_TEST_DRIVER NTSCFG_TEST_REGISTER(6); NTSCFG_TEST_REGISTER(7); NTSCFG_TEST_REGISTER(8); + NTSCFG_TEST_REGISTER(9); } NTSCFG_TEST_DRIVER_END; diff --git a/groups/nts/ntsu/ntsu_socketutil.cpp b/groups/nts/ntsu/ntsu_socketutil.cpp index 19528908..00b9d092 100644 --- a/groups/nts/ntsu/ntsu_socketutil.cpp +++ b/groups/nts/ntsu/ntsu_socketutil.cpp @@ -27,6 +27,7 @@ BSLS_IDENT_RCSID(ntsu_socketutil_cpp, "$Id$ $CSID$") #include #include #include +#include #include #include @@ -548,23 +549,23 @@ void IncomingDataStats::update(const struct iovec* buffersReceivable, // Provide a buffer suitable for attaching to a call to 'sendmsg' to send // control data to the peer of a socket. -class SendControl +class SendControl { enum { - // The payload size required to send any meta-data (viz. open file + // The payload size required to send any meta-data (viz. open file // descriptors) to the peer of a socket. k_SEND_CONTROL_PAYLOAD_SIZE = static_cast( - NTSU_SOCKETUTIL_MAX_HANDLES_PER_OUTGOING_CONTROLMSG * + NTSU_SOCKETUTIL_MAX_HANDLES_PER_OUTGOING_CONTROLMSG * sizeof(ntsa::Handle)), // The control buffer capacity required to send any meta-data (viz. // open file descriptors) to the peer of a socket. - k_SEND_CONTROL_BUFFER_SIZE = + k_SEND_CONTROL_BUFFER_SIZE = static_cast(CMSG_SPACE(k_SEND_CONTROL_PAYLOAD_SIZE)) }; // Define a type alias for a maximimally-aligned buffer of suitable size to - // send any meta-data (viz. open file descriptors) to the peer of the + // send any meta-data (viz. open file descriptors) to the peer of the // socket. typedef bsls::AlignedBuffer Arena; @@ -577,26 +578,27 @@ class SendControl public: // Create a new send control buffer. SendControl(); - + // Destroy this object. ~SendControl(); - - // Encode the specified 'options' into the control buffer. Return the + + // Encode the specified 'options' into the control buffer. Return the // error. ntsa::Error encode(msghdr* msg, const ntsa::SendOptions& options); }; // Provide a buffer suitable for attaching to a call to 'recvmsg' to receive // control data from the peer of a socket. -class ReceiveControl +class ReceiveControl { enum { - // The payload size required to receive any meta-data (e.g. open + // The payload size required to receive any meta-data (e.g. open // file descriptors, timestamps, etc.) buffered by the operating system // for a socket. - k_RECEIVE_CONTROL_PAYLOAD_SIZE = static_cast( - NTSU_SOCKETUTIL_MAX_HANDLES_PER_INCOMING_CONTROLMSG * - sizeof(ntsa::Handle)) + k_RECEIVE_CONTROL_PAYLOAD_SIZE = + static_cast( + NTSU_SOCKETUTIL_MAX_HANDLES_PER_INCOMING_CONTROLMSG * + sizeof(ntsa::Handle)) #if defined(BSLS_PLATFORM_OS_LINUX) + static_cast(sizeof(TimestampUtil::ScmTimestamping)) #endif @@ -604,7 +606,8 @@ class ReceiveControl // The control buffer capacity required to receive any meta-data (e.g. // open file descriptors, timestamps, etc.) buffered by the operating // system for a socket. - , k_RECEIVE_CONTROL_BUFFER_SIZE = + , + k_RECEIVE_CONTROL_BUFFER_SIZE = static_cast(CMSG_SPACE(k_RECEIVE_CONTROL_PAYLOAD_SIZE)) }; @@ -622,16 +625,16 @@ class ReceiveControl public: // Create a new receive control buffer. ReceiveControl(); - + // Destroy this object. ~ReceiveControl(); // Zero the control buffer. void initialize(msghdr* msg); - - // Decode the control buffer of specified 'msg' and decode its contents + + // Decode the control buffer of specified 'msg' and decode its contents // into the specified 'context' according to the specified 'options'. - ntsa::Error decode(ntsa::ReceiveContext* context, + ntsa::Error decode(ntsa::ReceiveContext* context, const msghdr& msg, const ntsa::ReceiveOptions& options); }; @@ -656,17 +659,17 @@ ntsa::Error SendControl::encode(msghdr* msg, const ntsa::SendOptions& options) msg->msg_control = d_arena.buffer(); msg->msg_controllen = static_cast(k_SEND_CONTROL_BUFFER_SIZE); - + ntsa::Handle foreignHandle = options.foreignHandle().value(); - struct cmsghdr *ctl = CMSG_FIRSTHDR(msg); + struct cmsghdr* ctl = CMSG_FIRSTHDR(msg); ctl->cmsg_level = SOL_SOCKET; ctl->cmsg_type = SCM_RIGHTS; ctl->cmsg_len = CMSG_LEN(sizeof foreignHandle); bsl::memcpy(CMSG_DATA(ctl), &foreignHandle, sizeof foreignHandle); - + return ntsa::Error(); } @@ -674,7 +677,7 @@ NTSCFG_INLINE ReceiveControl::ReceiveControl() { } - + NTSCFG_INLINE ReceiveControl::~ReceiveControl() { @@ -684,12 +687,12 @@ void ReceiveControl::initialize(msghdr* msg) { bsl::memset(d_arena.buffer(), 0, k_RECEIVE_CONTROL_BUFFER_SIZE); - msg->msg_control = d_arena.buffer(); - msg->msg_controllen = + msg->msg_control = d_arena.buffer(); + msg->msg_controllen = static_cast(k_RECEIVE_CONTROL_BUFFER_SIZE); } -ntsa::Error ReceiveControl::decode(ntsa::ReceiveContext* context, +ntsa::Error ReceiveControl::decode(ntsa::ReceiveContext* context, const msghdr& msg, const ntsa::ReceiveOptions& options) { @@ -703,8 +706,8 @@ ntsa::Error ReceiveControl::decode(ntsa::ReceiveContext* context, if (NTSCFG_UNLIKELY(hdr->cmsg_len != CMSG_LEN(sizeof fd))) { BSLS_LOG_WARN("Ignoring received control block meta-data: " "Unexpected control message payload size: " - "expected %d bytes, found %d bytes", - (int)(CMSG_LEN(sizeof fd)), + "expected %d bytes, found %d bytes", + (int)(CMSG_LEN(sizeof fd)), (int)(hdr->cmsg_len)); continue; } @@ -727,8 +730,8 @@ ntsa::Error ReceiveControl::decode(ntsa::ReceiveContext* context, if (NTSCFG_UNLIKELY(hdr->cmsg_len != CMSG_LEN(sizeof ts))) { BSLS_LOG_WARN("Ignoring received control block meta-data: " "Unexpected control message payload size: " - "expected %d bytes, found %d bytes", - (int)(CMSG_LEN(sizeof ts)), + "expected %d bytes, found %d bytes", + (int)(CMSG_LEN(sizeof ts)), (int)(hdr->cmsg_len)); continue; } @@ -752,8 +755,8 @@ ntsa::Error ReceiveControl::decode(ntsa::ReceiveContext* context, if (NTSCFG_UNLIKELY(hdr->cmsg_len != CMSG_LEN(sizeof ts))) { BSLS_LOG_WARN("Ignoring received control block meta-data: " "Unexpected control message payload size: " - "expected %d bytes, found %d bytes", - (int)(CMSG_LEN(sizeof ts)), + "expected %d bytes, found %d bytes", + (int)(CMSG_LEN(sizeof ts)), (int)(hdr->cmsg_len)); continue; } @@ -1386,8 +1389,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(size); - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1453,8 +1463,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(size); - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1517,8 +1534,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(numBytesTotal); - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1580,8 +1604,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(numBytesTotal); - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1643,8 +1674,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(numBytesTotal); - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1710,8 +1748,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(size); - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1774,8 +1819,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(numBytesTotal); - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1837,8 +1889,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(numBytesTotal); - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1899,9 +1958,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, msg.msg_iovlen = NTSU_SOCKETUTIL_MSG_IOV_LEN(numBuffersTotal); context->setBytesSendable(numBytesTotal); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -1928,7 +1993,7 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, const bool specifyEndpoint = !options.endpoint().isNull(); const bool specifyMetaData = !options.foreignHandle().isNull(); - + msghdr msg; bsl::memset(&msg, 0, sizeof msg); @@ -1966,9 +2031,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, msg.msg_iovlen = 1; context->setBytesSendable(size); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -2068,8 +2139,15 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, context->setBytesSendable(numBytesTotal); } - ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + + ssize_t sendmsgResult = ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -2135,8 +2213,16 @@ ntsa::Error SocketUtil::send(ntsa::SendContext* context, } } + int sendFlags = NTSU_SOCKETUTIL_SENDMSG_FLAGS; + +#if defined(BSLS_PLATFORM_OS_LINUX) + if (options.zeroCopy()) { + sendFlags |= ntsu::ZeroCopyUtil::e_MSG_ZEROCOPY; + } +#endif + ssize_t sendmsgResult = - ::sendmsg(socket, &msg, NTSU_SOCKETUTIL_SENDMSG_FLAGS); + ::sendmsg(socket, &msg, sendFlags); NTSU_SOCKETUTIL_DEBUG_SENDMSG_UPDATE(msg.msg_iov, msg.msg_iovlen, @@ -2702,7 +2788,7 @@ ntsa::Error SocketUtil::receive(ntsa::ReceiveContext* context, if (wantEndpoint) { SocketStorageUtil::initialize(&socketAddress, &socketAddressSize); - + msg.msg_name = &socketAddress; msg.msg_namelen = socketAddressSize; } @@ -2853,7 +2939,7 @@ ntsa::Error SocketUtil::receive(ntsa::ReceiveContext* context, if (wantEndpoint) { SocketStorageUtil::initialize(&socketAddress, &socketAddressSize); - + msg.msg_name = &socketAddress; msg.msg_namelen = socketAddressSize; } @@ -3224,19 +3310,25 @@ ntsa::Error SocketUtil::receiveNotifications( } } + // The socket error queue must be drained even if there is no space to + // store the notification. + if (notifications == 0) { continue; } - /* - It is assumed that timestamp information comes in pairs: meta data + timestamp message. - Meta data is sock_extend_err structure (cmsg_level == SOL_IP && cmsg_type == IP_RECVERR) - and timestamp information is (cmsg_level == SOL_SOCKET && hdr->cmsg_type == SO_TIMESTAMPING). + // Assume the timestamp information comes in pairs: meta data + + // timestamp message. + // Meta data is sock_extend_err structure: for IPv4: + // (cmsg_level == SOL_IP && cmsg_type == IP_RECVERR) and for IPv6: + // (cmsg_level == SOL_IPV6 && cmsg_type == IPV6_RECVERR) + // and timestamp information is (cmsg_level == SOL_SOCKET && + // hdr->cmsg_type == SO_TIMESTAMPING). + // + // The messages may be received in any order, e.g. + // IP_RECVERR -> SO_TIMESTAMPING or SO_TIMESTAMPING -> IP_RECVERR. - Code below should be ready to handle messages in any order, e.g. IP_RECVERR -> SO_TIMESTAMPING - or SO_TIMESTAMPING -> IP_RECVERR - */ - bool tsMetaDataReceied = false; + bool tsMetaDataReceived = false; bool timestampReceived = false; ntsa::Timestamp ts; @@ -3245,32 +3337,45 @@ ntsa::Error SocketUtil::receiveNotifications( for (cmsghdr* hdr = CMSG_FIRSTHDR(&msg); hdr != 0; hdr = CMSG_NXTHDR(&msg, hdr)) { - if (hdr->cmsg_level == SOL_IP && hdr->cmsg_type == IP_RECVERR) { + if ((hdr->cmsg_level == SOL_IP && hdr->cmsg_type == IP_RECVERR) || + (hdr->cmsg_level == SOL_IPV6 && + hdr->cmsg_type == IPV6_RECVERR)) + { sock_extended_err ser; std::memcpy(&ser, CMSG_DATA(hdr), sizeof(ser)); - ts.setId(ser.ee_data); - switch (ser.ee_info) { - case (TimestampUtil::e_SCM_TSTAMP_SCHED): { - ts.setType(ntsa::TimestampType::e_SCHEDULED); - } break; - case (TimestampUtil::e_SCM_TSTAMP_SND): { - ts.setType(ntsa::TimestampType::e_SENT); - } break; - case (TimestampUtil::e_SCM_TSTAMP_ACK): { - ts.setType(ntsa::TimestampType::e_ACKNOWLEDGED); - } break; - default: { - //error, drop timestamp - } - } - tsMetaDataReceied = true; - if (timestampReceived) { - notification.reset(); - notification.makeTimestamp(ts); + if (ser.ee_origin == SO_EE_ORIGIN_TIMESTAMPING) { + ts.setId(ser.ee_data); + switch (ser.ee_info) { + case (TimestampUtil::e_SCM_TSTAMP_SCHED): { + ts.setType(ntsa::TimestampType::e_SCHEDULED); + } break; + case (TimestampUtil::e_SCM_TSTAMP_SND): { + ts.setType(ntsa::TimestampType::e_SENT); + } break; + case (TimestampUtil::e_SCM_TSTAMP_ACK): { + ts.setType(ntsa::TimestampType::e_ACKNOWLEDGED); + } break; + default: { + //error, drop timestamp + } + } + tsMetaDataReceived = true; + if (timestampReceived) { + notification.reset(); + notification.makeTimestamp(ts); + + notifications->addNotification(notification); + tsMetaDataReceived = false; + timestampReceived = false; + } + } + else if (ser.ee_origin == + ntsu::ZeroCopyUtil::e_SO_EE_ORIGIN_ZEROCOPY) + { + ntsa::ZeroCopy zc(ser.ee_info, ser.ee_data, ser.ee_code); + notification.makeZeroCopy(zc); notifications->addNotification(notification); - tsMetaDataReceied = false; - timestampReceived = false; } } else if (hdr->cmsg_level == SOL_SOCKET && @@ -3286,23 +3391,24 @@ ntsa::Error SocketUtil::receiveNotifications( ts.setTime(ti); timestampReceived = true; - if (tsMetaDataReceied) { + if (tsMetaDataReceived) { notification.reset(); notification.makeTimestamp(ts); notifications->addNotification(notification); - tsMetaDataReceied = false; + tsMetaDataReceived = false; timestampReceived = false; } } else { BSLS_LOG_WARN( - "unexpected ctrl data received: cmsg_level=%d, " - "cmsg_type=%d, tsMetaDataReceied=%d, timestampReceived=%d", + "Unexpected control message received: cmsg_level = %d, " + "cmsg_type = %d, tsMetaDataReceied = %d, " + "timestampReceived = %d", hdr->cmsg_level, hdr->cmsg_type, - tsMetaDataReceied, + tsMetaDataReceived, timestampReceived); } } diff --git a/groups/nts/ntsu/ntsu_socketutil.t.cpp b/groups/nts/ntsu/ntsu_socketutil.t.cpp index b221fa49..c7a3865e 100644 --- a/groups/nts/ntsu/ntsu_socketutil.t.cpp +++ b/groups/nts/ntsu/ntsu_socketutil.t.cpp @@ -28,7 +28,10 @@ #include #include #include +#include #include +#include +#include #if defined(BSLS_PLATFORM_OS_WINDOWS) #define WIN32_LEAN_AND_MEAN @@ -136,6 +139,77 @@ bool supportsTxTimestamps(ntsa::Handle socket) #endif +void extractZeroCopyNotifications(bsl::list* zerocopy, + ntsa::Handle handle, + bslma::Allocator* allocator) +{ + ntsa::NotificationQueue notifications(allocator); + notifications.setHandle(handle); + + ntsa::Error error = + ntsu::SocketUtil::receiveNotifications(¬ifications, handle); + NTSCFG_TEST_OK(error); + + NTSCFG_TEST_LOG_DEBUG << notifications << NTSCFG_TEST_LOG_END; + + // save zerocopy notifications for later validation + for (bsl::vector::const_iterator it = + notifications.notifications().cbegin(); + it != notifications.notifications().cend(); + ++it) + { + NTSCFG_TEST_TRUE(it->isZeroCopy()); + zerocopy->push_back(it->zeroCopy()); + } +} + +void extractTimestampNotifications(bsl::list* ts, + ntsa::Handle handle, + bslma::Allocator* allocator) +{ + ntsa::NotificationQueue notifications(allocator); + notifications.setHandle(handle); + + ntsa::Error error = + ntsu::SocketUtil::receiveNotifications(¬ifications, handle); + NTSCFG_TEST_OK(error); + + NTSCFG_TEST_LOG_DEBUG << notifications << NTSCFG_TEST_LOG_END; + + // save zerocopy notifications for later validation + for (bsl::vector::const_iterator it = + notifications.notifications().cbegin(); + it != notifications.notifications().cend(); + ++it) + { + NTSCFG_TEST_TRUE(it->isTimestamp()); + ts->push_back(it->timestamp()); + } +} + +void extractNotifications(bsl::list* nt, + ntsa::Handle handle, + bslma::Allocator* allocator) +{ + ntsa::NotificationQueue notifications(allocator); + notifications.setHandle(handle); + + ntsa::Error error = + ntsu::SocketUtil::receiveNotifications(¬ifications, handle); + NTSCFG_TEST_OK(error); + + NTSCFG_TEST_LOG_DEBUG << notifications << NTSCFG_TEST_LOG_END; + + // save zerocopy notifications for later validation + for (bsl::vector::const_iterator it = + notifications.notifications().cbegin(); + it != notifications.notifications().cend(); + ++it) + { + nt->push_back(*it); + } +} + /// This typedef defines a callback function invoked to test a particular /// portion of the component using the specified connected 'server' and /// 'client' having the specified stream socket 'transport', supplying @@ -1832,233 +1906,823 @@ void testDatagramSocketTransmissionMultipleMessages( } } -/// Comparator with is used to help sorting Timestamps according to their time -/// value. -struct TimestampTimeComparator { - /// Return true if the specified 'a' coccured ealier than the specified 'b' - /// Otherwise return false. - bool operator()(const ntsa::Timestamp& a, const ntsa::Timestamp& b) - { - return a.time() < b.time(); +void testStreamSocketMsgZeroCopy(ntsa::Transport::Value transport, + ntsa::Handle server, + ntsa::Handle client, + bslma::Allocator* allocator) +{ + if (transport == ntsa::Transport::e_LOCAL_STREAM) { + return; } -}; - -} // close namespace 'test' -NTSCFG_TEST_CASE(1) -{ - // Concern: Stream socket breathing test, which also serves as the - // usage example. - // - // Plan: + NTSCFG_TEST_LOG_DEBUG << "Testing " << transport << NTSCFG_TEST_LOG_END; - ntsa::Error error; + // Note: for this test case msgSize is not really important as loopback + // device is used - it means that even if MSG_ZEROCOPY option is used then + // anyway data will be copied - bsl::vector socketTypes; + const int msgSize = 200; + const int numMessagesToSend = 200; - if (ntsu::AdapterUtil::supportsTransport( - ntsa::Transport::e_TCP_IPV4_STREAM)) - { - socketTypes.push_back(ntsa::Transport::e_TCP_IPV4_STREAM); - } + ntsa::Error error; - if (ntsu::AdapterUtil::supportsTransport( - ntsa::Transport::e_TCP_IPV6_STREAM)) - { - socketTypes.push_back(ntsa::Transport::e_TCP_IPV6_STREAM); - } + error = ntsu::SocketOptionUtil::setZeroCopy(client, true); + NTSCFG_TEST_OK(error); - if (ntsu::AdapterUtil::supportsTransport(ntsa::Transport::e_LOCAL_STREAM)) - { - socketTypes.push_back(ntsa::Transport::e_LOCAL_STREAM); + bsl::vector message(msgSize, allocator); + for (int i = 0; i < msgSize; ++i) { + message[i] = bsl::rand() % 100; } + const ntsa::Data data(ntsa::ConstBuffer(message.data(), message.size())); - for (bsl::size_t i = 0; i < socketTypes.size(); ++i) { - ntsa::Transport::Value transport = socketTypes[i]; - - // Create a blocking socket, bind it to any port on the loopback - // address, then begin listening for connections. + bsl::list feedback(allocator); + bsl::unordered_set sendIDs(allocator); - ntsa::Handle listener; - error = ntsu::SocketUtil::create(&listener, transport); - NTSCFG_TEST_ASSERT(!error); + for (int i = 0; i < numMessagesToSend; ++i) { + ntsa::SendContext context; + ntsa::SendOptions options; + options.setZeroCopy(true); - if (transport == ntsa::Transport::e_TCP_IPV4_STREAM) { - error = ntsu::SocketUtil::bind( - ntsa::Endpoint(ntsa::Ipv4Address::loopback(), 0), - false, - listener); - NTSCFG_TEST_ASSERT(!error); - } - else if (transport == ntsa::Transport::e_TCP_IPV6_STREAM) { - error = ntsu::SocketUtil::bind( - ntsa::Endpoint(ntsa::Ipv6Address::loopback(), 0), - false, - listener); - NTSCFG_TEST_ASSERT(!error); + error = ntsu::SocketUtil::send(&context, data, options, client); + if (error == ntsa::Error(ntsa::Error::e_WOULD_BLOCK) || + error == ntsa::Error(ntsa::Error::e_LIMIT)) + { + --i; + continue; } - else if (transport == ntsa::Transport::e_LOCAL_STREAM) { - ntsa::LocalName localName; - error = ntsa::LocalName::generateUnique(&localName); - NTSCFG_TEST_ASSERT(!error); + NTSCFG_TEST_OK(error); + sendIDs.insert(i); - error = ntsu::SocketUtil::bind(ntsa::Endpoint(localName), - false, - listener); - NTSCFG_TEST_ASSERT(!error); - } - else { - NTSCFG_TEST_TRUE(false); - } + NTSCFG_TEST_ASSERT(context.bytesSendable() == msgSize); + NTSCFG_TEST_ASSERT(context.bytesSent() == msgSize); - error = ntsu::SocketUtil::listen(1, listener); - NTSCFG_TEST_ASSERT(!error); + test::extractZeroCopyNotifications(&feedback, client, allocator); + } - // Create a blocking socket for the client, then connect that socket to - // the listener socket's local endpoint. + // receive data + { + bsl::vector rBuffer(msgSize, allocator); + for (int totalSend = msgSize * numMessagesToSend; totalSend > 0;) { + ntsa::ReceiveContext context; + ntsa::ReceiveOptions options; - ntsa::Handle client; - error = ntsu::SocketUtil::create(&client, transport); - NTSCFG_TEST_ASSERT(!error); + ntsa::Data data( + ntsa::MutableBuffer(rBuffer.data(), rBuffer.size())); - ntsa::Endpoint listenerEndpoint; - error = ntsu::SocketUtil::sourceEndpoint(&listenerEndpoint, listener); - NTSCFG_TEST_ASSERT(!error); + error = + ntsu::SocketUtil::receive(&context, &data, options, server); + if (!error) { + totalSend -= context.bytesReceived(); + } + } + } - error = ntsu::SocketUtil::connect(listenerEndpoint, client); - NTSCFG_TEST_ASSERT(!error); + // retrieve data from the socket error queue until all send system + // calls are acknowledged by the OS + while (!sendIDs.empty()) { + test::extractZeroCopyNotifications(&feedback, client, allocator); - // Create a blocking socket for the server by accepting the connection - // made to the listener socket. + while (!feedback.empty()) { + const ntsa::ZeroCopy& zc = feedback.front(); + NTSCFG_TEST_EQ(zc.code(), 1); // we know that OS copied data + if (zc.from() == zc.to()) { + NTSCFG_TEST_EQ(sendIDs.erase(zc.from()), 1); + } + else { + for (bsl::uint32_t i = zc.from(); i != (zc.to() + 1); ++i) { + NTSCFG_TEST_EQ(sendIDs.erase(i), 1); + } + } + feedback.pop_front(); + } + } +} - ntsa::Handle server; - error = ntsu::SocketUtil::accept(&server, listener); - NTSCFG_TEST_ASSERT(!error); +void testDatagramSocketTxTimestamps(ntsa::Transport::Value transport, + ntsa::Handle server, + const ntsa::Endpoint& serverEndpoint, + ntsa::Handle client, + const ntsa::Endpoint& clientEndpoint, + bslma::Allocator* allocator) +{ + if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { + return; + } - // Get the client source and remote endpoints. + if (!ntscfg::Platform::supportsTimestamps()) { + return; + } - ntsa::Endpoint clientSourceEndpoint; - error = - ntsu::SocketUtil::sourceEndpoint(&clientSourceEndpoint, client); - NTSCFG_TEST_ASSERT(!error); + NTSCFG_TEST_LOG_DEBUG << "Testing " << transport << NTSCFG_TEST_LOG_END; - ntsa::Endpoint clientRemoteEndpoint; - error = - ntsu::SocketUtil::remoteEndpoint(&clientRemoteEndpoint, client); - NTSCFG_TEST_ASSERT(!error); + ntsa::Error error; - // Get the server source and remote endpoints. + error = ntsu::SocketOptionUtil::setTimestampOutgoingData(client, true); - ntsa::Endpoint serverSourceEndpoint; - error = - ntsu::SocketUtil::sourceEndpoint(&serverSourceEndpoint, server); - NTSCFG_TEST_ASSERT(!error); + NTSCFG_TEST_OK(error); - ntsa::Endpoint serverRemoteEndpoint; - error = - ntsu::SocketUtil::remoteEndpoint(&serverRemoteEndpoint, server); - NTSCFG_TEST_ASSERT(!error); + const int msgSize = 200; + const int numMessagesToSend = 100; - if (NTSCFG_TEST_VERBOSITY) { - bsl::cout << "Listener at " << listenerEndpoint << bsl::endl; + bsl::vector message(msgSize, allocator); + for (int i = 0; i < msgSize; ++i) { + message[i] = bsl::rand() % 100; + } + const ntsa::Data data(ntsa::ConstBuffer(message.data(), message.size())); - bsl::cout << "Client at " << clientSourceEndpoint << " to " - << clientRemoteEndpoint << bsl::endl; + bsl::list feedback(allocator); - bsl::cout << "Server at " << serverSourceEndpoint << " to " - << serverRemoteEndpoint << bsl::endl; - } + // for each TS id there is a map of each expected TS type and a reference + // time + bsl::unordered_map< + bsl::uint32_t, + bsl::unordered_map > + timestampsToValidate(allocator); - // Enqueue outgoing data to transmit by the client socket. + // Enqueue outgoing data to transmit by the client socket. - { - char buffer = 'C'; - ntsa::SendContext context; - ntsa::SendOptions options; + for (int i = 0; i < numMessagesToSend; ++i) { + ntsa::SendContext context; + ntsa::SendOptions options; + options.setEndpoint(serverEndpoint); - ntsa::Data data(ntsa::ConstBuffer(&buffer, 1)); + const bsls::TimeInterval sysTimeBeforeSending = + bdlt::CurrentTime::now(); + error = ntsu::SocketUtil::send(&context, data, options, client); + if (error == ntsa::Error(ntsa::Error::e_WOULD_BLOCK) || + error == ntsa::Error(ntsa::Error::e_LIMIT)) + { + --i; + continue; + } + NTSCFG_TEST_OK(error); - error = ntsu::SocketUtil::send(&context, data, options, client); - NTSCFG_TEST_ASSERT(!error); + timestampsToValidate[i][ntsa::TimestampType::e_SENT] = + sysTimeBeforeSending; + timestampsToValidate[i][ntsa::TimestampType::e_SCHEDULED] = + sysTimeBeforeSending; - NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); - NTSCFG_TEST_ASSERT(context.bytesSent() == 1); - } + NTSCFG_TEST_ASSERT(context.bytesSendable() == msgSize); + NTSCFG_TEST_ASSERT(context.bytesSent() == msgSize); - // Dequeue incoming data received by the server socket. + test::extractTimestampNotifications(&feedback, client, allocator); + } - { - char buffer; + // receive data + { + bsl::vector rBuffer(msgSize, allocator); + for (int totalSend = msgSize * numMessagesToSend; totalSend > 0;) { ntsa::ReceiveContext context; ntsa::ReceiveOptions options; - ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); + ntsa::Data data( + ntsa::MutableBuffer(rBuffer.data(), rBuffer.size())); error = ntsu::SocketUtil::receive(&context, &data, options, server); - NTSCFG_TEST_ASSERT(!error); + if (!error) { + totalSend -= context.bytesReceived(); + } + } + } - NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); - NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); - NTSCFG_TEST_ASSERT(buffer == 'C'); + // retrieve data from the socket error queue until all send system + // calls related timestamps received + while (!timestampsToValidate.empty()) { + test::extractTimestampNotifications(&feedback, client, allocator); + + while (!feedback.empty()) { + const ntsa::Timestamp& ts = feedback.front(); + NTSCFG_TEST_EQ(timestampsToValidate.count(ts.id()), 1); + NTSCFG_TEST_EQ(timestampsToValidate[ts.id()].count(ts.type()), 1); + NTSCFG_TEST_LT(timestampsToValidate[ts.id()][ts.type()], + ts.time()); + timestampsToValidate[ts.id()].erase(ts.type()); + if (timestampsToValidate[ts.id()].empty()) { + timestampsToValidate.erase(ts.id()); + } + feedback.pop_front(); } + } +} - // Enqueue outgoing data to transmit by the server socket. +void testStreamSocketTxTimestamps(ntsa::Transport::Value transport, + ntsa::Handle server, + ntsa::Handle client, + bslma::Allocator* allocator) +{ + if (transport == ntsa::Transport::e_LOCAL_STREAM) { + return; + } - { - char buffer = 'S'; - ntsa::SendContext context; - ntsa::SendOptions options; + if (!ntscfg::Platform::supportsTimestamps()) { + return; + } - ntsa::Data data(ntsa::ConstBuffer(&buffer, 1)); + NTSCFG_TEST_LOG_DEBUG << "Testing " << transport << NTSCFG_TEST_LOG_END; - error = ntsu::SocketUtil::send(&context, data, options, server); - NTSCFG_TEST_ASSERT(!error); + ntsa::Error error; - NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); - NTSCFG_TEST_ASSERT(context.bytesSent() == 1); - } + error = ntsu::SocketOptionUtil::setTimestampOutgoingData(client, true); - // Dequeue incoming data received by the client socket. + NTSCFG_TEST_OK(error); - { - char buffer; - ntsa::ReceiveContext context; - ntsa::ReceiveOptions options; + const int msgSize = 200; + const int numMessagesToSend = 5; - ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); + bsl::vector message(msgSize, allocator); + for (int i = 0; i < msgSize; ++i) { + message[i] = bsl::rand() % 100; + } + const ntsa::Data data(ntsa::ConstBuffer(message.data(), message.size())); - error = - ntsu::SocketUtil::receive(&context, &data, options, client); - NTSCFG_TEST_ASSERT(!error); + bsl::list feedback(allocator); - NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); - NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); - NTSCFG_TEST_ASSERT(buffer == 'S'); + // for each TS id there is a map of each expected TS type and a reference + // time + bsl::unordered_map< + bsl::uint32_t, + bsl::unordered_map > + timestampsToValidate(allocator); + + bsl::uint32_t byteCounter = 0; + for (int i = 0; i < numMessagesToSend; ++i) { + ntsa::SendContext context; + ntsa::SendOptions options; + options.setZeroCopy(true); + + const bsls::TimeInterval sysTimeBeforeSending = + bdlt::CurrentTime::now(); + error = ntsu::SocketUtil::send(&context, data, options, client); + if (error == ntsa::Error(ntsa::Error::e_WOULD_BLOCK) || + error == ntsa::Error(ntsa::Error::e_LIMIT)) + { + --i; + continue; } + NTSCFG_TEST_OK(error); - // Shutdown writing by the client socket. + NTSCFG_TEST_ASSERT(context.bytesSendable() == msgSize); + NTSCFG_TEST_ASSERT(context.bytesSent() == msgSize); - error = ntsu::SocketUtil::shutdown(ntsa::ShutdownType::e_SEND, client); - NTSCFG_TEST_ASSERT(!error); + byteCounter += msgSize; - // Dequeue incoming data received by the server socket, and observe - // that zero bytes are successfully dequeued, indicating the client - // socket has shut down writing from its side of the connection. + timestampsToValidate[byteCounter - 1][ntsa::TimestampType::e_SENT] = + sysTimeBeforeSending; + timestampsToValidate[byteCounter - 1] + [ntsa::TimestampType::e_SCHEDULED] = + sysTimeBeforeSending; + timestampsToValidate[byteCounter - 1] + [ntsa::TimestampType::e_ACKNOWLEDGED] = + sysTimeBeforeSending; - { - char buffer; + test::extractTimestampNotifications(&feedback, client, allocator); + } + + // receive data + { + bsl::vector rBuffer(msgSize, allocator); + for (int totalSend = msgSize * numMessagesToSend; totalSend > 0;) { ntsa::ReceiveContext context; ntsa::ReceiveOptions options; - ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); + ntsa::Data data( + ntsa::MutableBuffer(rBuffer.data(), rBuffer.size())); error = ntsu::SocketUtil::receive(&context, &data, options, server); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); - NTSCFG_TEST_ASSERT(context.bytesReceived() == 0); + if (!error) { + totalSend -= context.bytesReceived(); + } + } + } + + // retrieve data from the socket error queue until all send system + // calls related timestamps received + while (!timestampsToValidate.empty()) { + test::extractTimestampNotifications(&feedback, client, allocator); + + while (!feedback.empty()) { + const ntsa::Timestamp& ts = feedback.front(); + NTSCFG_TEST_EQ(timestampsToValidate.count(ts.id()), 1); + NTSCFG_TEST_EQ(timestampsToValidate[ts.id()].count(ts.type()), 1); + NTSCFG_TEST_LT(timestampsToValidate[ts.id()][ts.type()], + ts.time()); + timestampsToValidate[ts.id()].erase(ts.type()); + if (timestampsToValidate[ts.id()].empty()) { + timestampsToValidate.erase(ts.id()); + } + feedback.pop_front(); + } + } +} + +void testDatagramSocketTxTimestampsAndZeroCopy( + ntsa::Transport::Value transport, + ntsa::Handle server, + const ntsa::Endpoint& serverEndpoint, + ntsa::Handle client, + const ntsa::Endpoint& clientEndpoint, + bslma::Allocator* allocator) +{ + if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { + return; + } + + if (!ntscfg::Platform::supportsTimestamps()) { + return; + } + + NTSCFG_TEST_LOG_DEBUG << "Testing " << transport << NTSCFG_TEST_LOG_END; + + ntsa::Error error; + + error = ntsu::SocketOptionUtil::setTimestampOutgoingData(client, true); + NTSCFG_TEST_OK(error); + + error = ntsu::SocketOptionUtil::setZeroCopy(client, true); + NTSCFG_TEST_OK(error); + + // Note that zero-copy requires significantly more resources on the + // receiver's side. Sending a large number of bigger datagram may exceed + // the receiver's capacity to receive them, resulting in successful sends + // but dropped datagrams, resulting in the receiver blocking indefinitely + // receiving datagram from the receive buffer. + + const int msgSize = 100; + const int numMessagesToSend = 10; + + bsl::vector message(msgSize, allocator); + for (int i = 0; i < msgSize; ++i) { + message[i] = bsl::rand() % 100; + } + const ntsa::Data data(ntsa::ConstBuffer(message.data(), message.size())); + + bsl::list feedback(allocator); + + // for each TS id there is a map of each expected TS type and a reference + // time + bsl::unordered_map< + bsl::uint32_t, + bsl::unordered_map > + timestampsToValidate(allocator); + bsl::unordered_set zeroCopyToValidate(allocator); + + // Enqueue outgoing data to transmit by the client socket. + + for (int i = 0; i < numMessagesToSend; ++i) { + ntsa::SendContext context; + ntsa::SendOptions options; + options.setEndpoint(serverEndpoint); + options.setZeroCopy(true); + + const bsls::TimeInterval sysTimeBeforeSending = + bdlt::CurrentTime::now(); + error = ntsu::SocketUtil::send(&context, data, options, client); + if (error == ntsa::Error(ntsa::Error::e_WOULD_BLOCK) || + error == ntsa::Error(ntsa::Error::e_LIMIT)) + { + --i; + continue; + } + NTSCFG_TEST_OK(error); + + timestampsToValidate[i][ntsa::TimestampType::e_SENT] = + sysTimeBeforeSending; + timestampsToValidate[i][ntsa::TimestampType::e_SCHEDULED] = + sysTimeBeforeSending; + zeroCopyToValidate.insert(i); + + NTSCFG_TEST_ASSERT(context.bytesSendable() == msgSize); + NTSCFG_TEST_ASSERT(context.bytesSent() == msgSize); + + test::extractNotifications(&feedback, client, allocator); + } + + // receive data + { + bsl::vector rBuffer(msgSize, allocator); + for (int totalSend = msgSize * numMessagesToSend; totalSend > 0;) { + ntsa::ReceiveContext context; + ntsa::ReceiveOptions options; + + ntsa::Data data( + ntsa::MutableBuffer(rBuffer.data(), rBuffer.size())); + + error = + ntsu::SocketUtil::receive(&context, &data, options, server); + if (!error) { + totalSend -= context.bytesReceived(); + } + } + } + + // retrieve data from the socket error queue until all send system + // calls related timestamps received + while (!timestampsToValidate.empty() || !zeroCopyToValidate.empty()) { + test::extractNotifications(&feedback, client, allocator); + + while (!feedback.empty()) { + const ntsa::Notification& nt = feedback.front(); + if (nt.isTimestamp()) { + const ntsa::Timestamp& ts = nt.timestamp(); + NTSCFG_TEST_EQ(timestampsToValidate.count(ts.id()), 1); + NTSCFG_TEST_EQ(timestampsToValidate[ts.id()].count(ts.type()), + 1); + NTSCFG_TEST_LT(timestampsToValidate[ts.id()][ts.type()], + ts.time()); + timestampsToValidate[ts.id()].erase(ts.type()); + if (timestampsToValidate[ts.id()].empty()) { + timestampsToValidate.erase(ts.id()); + } + } + else if (nt.isZeroCopy()) { + const ntsa::ZeroCopy& zc = nt.zeroCopy(); + NTSCFG_TEST_EQ(zc.code(), 1); + if (zc.from() == zc.to()) { + NTSCFG_TEST_EQ(zeroCopyToValidate.erase(zc.from()), 1); + } + else { + for (bsl::uint32_t i = zc.from(); i != (zc.to() + 1); ++i) + { + NTSCFG_TEST_EQ(zeroCopyToValidate.erase(i), 1); + } + } + } + else { + NTSCFG_TEST_ASSERT(false); + } + feedback.pop_front(); + } + } +} + +void testStreamSocketTxTimestampsAndZeroCopy(ntsa::Transport::Value transport, + ntsa::Handle server, + ntsa::Handle client, + bslma::Allocator* allocator) +{ + if (transport == ntsa::Transport::e_LOCAL_STREAM) { + return; + } + + if (!ntscfg::Platform::supportsTimestamps()) { + return; + } + + NTSCFG_TEST_LOG_DEBUG << "Testing " << transport << NTSCFG_TEST_LOG_END; + + ntsa::Error error; + + error = ntsu::SocketOptionUtil::setTimestampOutgoingData(client, true); + NTSCFG_TEST_OK(error); + + error = ntsu::SocketOptionUtil::setZeroCopy(client, true); + NTSCFG_TEST_OK(error); + + const int msgSize = 200; + const int numMessagesToSend = 5; + + bsl::vector message(msgSize, allocator); + for (int i = 0; i < msgSize; ++i) { + message[i] = bsl::rand() % 100; + } + const ntsa::Data data(ntsa::ConstBuffer(message.data(), message.size())); + + bsl::list feedback(allocator); + + // for each TS id there is a map of each expected TS type and a reference + // time + bsl::unordered_map< + bsl::uint32_t, + bsl::unordered_map > + timestampsToValidate(allocator); + bsl::unordered_set zeroCopyToValidate(allocator); + + // Enqueue outgoing data to transmit by the client socket. + + bsl::uint32_t byteCounter = 0; + for (int i = 0; i < numMessagesToSend; ++i) { + ntsa::SendContext context; + ntsa::SendOptions options; + options.setZeroCopy(true); + + const bsls::TimeInterval sysTimeBeforeSending = + bdlt::CurrentTime::currentTimeDefault(); + if (sysTimeBeforeSending.totalSeconds() > 0) { + error = ntsu::SocketUtil::send(&context, data, options, client); + if (error == ntsa::Error(ntsa::Error::e_WOULD_BLOCK) || + error == ntsa::Error(ntsa::Error::e_LIMIT)) + { + --i; + continue; + } + } + NTSCFG_TEST_OK(error); + + byteCounter += msgSize; + + timestampsToValidate[byteCounter - 1][ntsa::TimestampType::e_SENT] = + sysTimeBeforeSending; + timestampsToValidate[byteCounter - 1] + [ntsa::TimestampType::e_SCHEDULED] = + sysTimeBeforeSending; + timestampsToValidate[byteCounter - 1] + [ntsa::TimestampType::e_ACKNOWLEDGED] = + sysTimeBeforeSending; + zeroCopyToValidate.insert(i); + + NTSCFG_TEST_ASSERT(context.bytesSendable() == msgSize); + NTSCFG_TEST_ASSERT(context.bytesSent() == msgSize); + + test::extractNotifications(&feedback, client, allocator); + } + + // receive data + { + bsl::vector rBuffer(msgSize, allocator); + for (int totalSend = msgSize * numMessagesToSend; totalSend > 0;) { + ntsa::ReceiveContext context; + ntsa::ReceiveOptions options; + + ntsa::Data data( + ntsa::MutableBuffer(rBuffer.data(), rBuffer.size())); + + error = + ntsu::SocketUtil::receive(&context, &data, options, server); + if (!error) { + totalSend -= context.bytesReceived(); + } + } + } + + // retrieve data from the socket error queue until all send system + // calls related timestamps received + while (!timestampsToValidate.empty() || !zeroCopyToValidate.empty()) { + test::extractNotifications(&feedback, client, allocator); + + while (!feedback.empty()) { + const ntsa::Notification& nt = feedback.front(); + if (nt.isTimestamp()) { + const ntsa::Timestamp& ts = nt.timestamp(); + NTSCFG_TEST_EQ(timestampsToValidate.count(ts.id()), 1); + NTSCFG_TEST_EQ(timestampsToValidate[ts.id()].count(ts.type()), + 1); + NTSCFG_TEST_LT(timestampsToValidate[ts.id()][ts.type()], + ts.time()); + timestampsToValidate[ts.id()].erase(ts.type()); + if (timestampsToValidate[ts.id()].empty()) { + timestampsToValidate.erase(ts.id()); + } + } + else if (nt.isZeroCopy()) { + const ntsa::ZeroCopy& zc = nt.zeroCopy(); + NTSCFG_TEST_EQ(zc.code(), 1); + if (zc.from() == zc.to()) { + NTSCFG_TEST_EQ(zeroCopyToValidate.erase(zc.from()), 1); + } + else { + for (bsl::uint32_t i = zc.from(); i != (zc.to() + 1); ++i) + { + NTSCFG_TEST_EQ(zeroCopyToValidate.erase(i), 1); + } + } + } + else { + NTSCFG_TEST_ASSERT(false); + } + feedback.pop_front(); + } + } +} + +/// Comparator with is used to help sorting Timestamps according to their time +/// value. +struct TimestampTimeComparator { + /// Return true if the specified 'a' occurred earlier than the specified + /// 'b'. Otherwise return false. + bool operator()(const ntsa::Timestamp& a, const ntsa::Timestamp& b) + { + return a.time() < b.time(); + } +}; + +} // close namespace 'test' + +NTSCFG_TEST_CASE(1) +{ + // Concern: Stream socket breathing test, which also serves as the + // usage example. + // + // Plan: + + ntsa::Error error; + + bsl::vector socketTypes; + + if (ntsu::AdapterUtil::supportsTransport( + ntsa::Transport::e_TCP_IPV4_STREAM)) + { + socketTypes.push_back(ntsa::Transport::e_TCP_IPV4_STREAM); + } + + if (ntsu::AdapterUtil::supportsTransport( + ntsa::Transport::e_TCP_IPV6_STREAM)) + { + socketTypes.push_back(ntsa::Transport::e_TCP_IPV6_STREAM); + } + + if (ntsu::AdapterUtil::supportsTransport(ntsa::Transport::e_LOCAL_STREAM)) + { + socketTypes.push_back(ntsa::Transport::e_LOCAL_STREAM); + } + + for (bsl::size_t i = 0; i < socketTypes.size(); ++i) { + ntsa::Transport::Value transport = socketTypes[i]; + + // Create a blocking socket, bind it to any port on the loopback + // address, then begin listening for connections. + + ntsa::Handle listener; + error = ntsu::SocketUtil::create(&listener, transport); + NTSCFG_TEST_ASSERT(!error); + + if (transport == ntsa::Transport::e_TCP_IPV4_STREAM) { + error = ntsu::SocketUtil::bind( + ntsa::Endpoint(ntsa::Ipv4Address::loopback(), 0), + false, + listener); + NTSCFG_TEST_ASSERT(!error); + } + else if (transport == ntsa::Transport::e_TCP_IPV6_STREAM) { + error = ntsu::SocketUtil::bind( + ntsa::Endpoint(ntsa::Ipv6Address::loopback(), 0), + false, + listener); + NTSCFG_TEST_ASSERT(!error); + } + else if (transport == ntsa::Transport::e_LOCAL_STREAM) { + ntsa::LocalName localName; + error = ntsa::LocalName::generateUnique(&localName); + NTSCFG_TEST_ASSERT(!error); + + error = ntsu::SocketUtil::bind(ntsa::Endpoint(localName), + false, + listener); + NTSCFG_TEST_ASSERT(!error); + } + else { + NTSCFG_TEST_TRUE(false); + } + + error = ntsu::SocketUtil::listen(1, listener); + NTSCFG_TEST_ASSERT(!error); + + // Create a blocking socket for the client, then connect that socket to + // the listener socket's local endpoint. + + ntsa::Handle client; + error = ntsu::SocketUtil::create(&client, transport); + NTSCFG_TEST_ASSERT(!error); + + ntsa::Endpoint listenerEndpoint; + error = ntsu::SocketUtil::sourceEndpoint(&listenerEndpoint, listener); + NTSCFG_TEST_ASSERT(!error); + + error = ntsu::SocketUtil::connect(listenerEndpoint, client); + NTSCFG_TEST_ASSERT(!error); + + // Create a blocking socket for the server by accepting the connection + // made to the listener socket. + + ntsa::Handle server; + error = ntsu::SocketUtil::accept(&server, listener); + NTSCFG_TEST_ASSERT(!error); + + // Get the client source and remote endpoints. + + ntsa::Endpoint clientSourceEndpoint; + error = + ntsu::SocketUtil::sourceEndpoint(&clientSourceEndpoint, client); + NTSCFG_TEST_ASSERT(!error); + + ntsa::Endpoint clientRemoteEndpoint; + error = + ntsu::SocketUtil::remoteEndpoint(&clientRemoteEndpoint, client); + NTSCFG_TEST_ASSERT(!error); + + // Get the server source and remote endpoints. + + ntsa::Endpoint serverSourceEndpoint; + error = + ntsu::SocketUtil::sourceEndpoint(&serverSourceEndpoint, server); + NTSCFG_TEST_ASSERT(!error); + + ntsa::Endpoint serverRemoteEndpoint; + error = + ntsu::SocketUtil::remoteEndpoint(&serverRemoteEndpoint, server); + NTSCFG_TEST_ASSERT(!error); + + if (NTSCFG_TEST_VERBOSITY) { + bsl::cout << "Listener at " << listenerEndpoint << bsl::endl; + + bsl::cout << "Client at " << clientSourceEndpoint << " to " + << clientRemoteEndpoint << bsl::endl; + + bsl::cout << "Server at " << serverSourceEndpoint << " to " + << serverRemoteEndpoint << bsl::endl; + } + + // Enqueue outgoing data to transmit by the client socket. + + { + char buffer = 'C'; + ntsa::SendContext context; + ntsa::SendOptions options; + + ntsa::Data data(ntsa::ConstBuffer(&buffer, 1)); + + error = ntsu::SocketUtil::send(&context, data, options, client); + NTSCFG_TEST_ASSERT(!error); + + NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); + NTSCFG_TEST_ASSERT(context.bytesSent() == 1); + } + + // Dequeue incoming data received by the server socket. + + { + char buffer; + ntsa::ReceiveContext context; + ntsa::ReceiveOptions options; + + ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); + + error = + ntsu::SocketUtil::receive(&context, &data, options, server); + NTSCFG_TEST_ASSERT(!error); + + NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); + NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); + NTSCFG_TEST_ASSERT(buffer == 'C'); + } + + // Enqueue outgoing data to transmit by the server socket. + + { + char buffer = 'S'; + ntsa::SendContext context; + ntsa::SendOptions options; + + ntsa::Data data(ntsa::ConstBuffer(&buffer, 1)); + + error = ntsu::SocketUtil::send(&context, data, options, server); + NTSCFG_TEST_ASSERT(!error); + + NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); + NTSCFG_TEST_ASSERT(context.bytesSent() == 1); + } + + // Dequeue incoming data received by the client socket. + + { + char buffer; + ntsa::ReceiveContext context; + ntsa::ReceiveOptions options; + + ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); + + error = + ntsu::SocketUtil::receive(&context, &data, options, client); + NTSCFG_TEST_ASSERT(!error); + + NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); + NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); + NTSCFG_TEST_ASSERT(buffer == 'S'); + } + + // Shutdown writing by the client socket. + + error = ntsu::SocketUtil::shutdown(ntsa::ShutdownType::e_SEND, client); + NTSCFG_TEST_ASSERT(!error); + + // Dequeue incoming data received by the server socket, and observe + // that zero bytes are successfully dequeued, indicating the client + // socket has shut down writing from its side of the connection. + + { + char buffer; + ntsa::ReceiveContext context; + ntsa::ReceiveOptions options; + + ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); + + error = + ntsu::SocketUtil::receive(&context, &data, options, server); + NTSCFG_TEST_ASSERT(!error); + + NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); + NTSCFG_TEST_ASSERT(context.bytesReceived() == 0); } // Shutdown writing by the server socket. @@ -6271,173 +6935,8 @@ NTSCFG_TEST_CASE(17) ntsu::SocketOptionUtil::setTimestampIncomingData(client, true); #if defined(BSLS_PLATFORM_OS_LINUX) NTSCFG_TEST_OK(error); - // sleep for 100 ms to let the kernel apply changes - bslmt::ThreadUtil::microSleep(100000, 0); -#else - NTSCFG_TEST_ERROR(error, ntsa::Error::e_NOT_IMPLEMENTED); -#endif - - // Enqueue outgoing data to transmit by the client socket. - - bsls::TimeInterval sysTimeBeforeSending; - { - char buffer = 'S'; - ntsa::SendContext context; - ntsa::SendOptions options; - - ntsa::Data data(ntsa::ConstBuffer(&buffer, 1)); - - sysTimeBeforeSending = bdlt::CurrentTime::now(); - - error = - ntsu::SocketUtil::send(&context, data, options, server); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); - NTSCFG_TEST_ASSERT(context.bytesSent() == 1); - } - - // Dequeue incoming data received by the server socket. - - { - char buffer; - ntsa::ReceiveContext context; - ntsa::ReceiveOptions options; - options.showTimestamp(); - - ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); - - error = ntsu::SocketUtil::receive(&context, - &data, - options, - client); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); - NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); - NTSCFG_TEST_ASSERT(buffer == 'S'); -#if defined(BSLS_PLATFORM__OS_LINUX) - int major, minor, patch, build; - NTSCFG_TEST_ASSERT(ntsscm::Version::systemVersion(&major, - &minor, - &patch, - &build) == - 0); - - // Linux kernels versions <= 3.10.0 have restricted - // timestamping support. - - if (KERNEL_VERSION(major, minor, patch) <= - KERNEL_VERSION(3, 10, 0)) - { - NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); - NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); - } - else { - // On a modern kernel timestamps will be present for - // AF_INET stream sockets only. - - if (transport == ntsa::Transport::e_LOCAL_STREAM) { - NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); - NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); - } - else { - BSLS_LOG_DEBUG("Detected RX timestamp"); - NTSCFG_TEST_TRUE( - context.softwareTimestamp().has_value()); - NTSCFG_TEST_LE(sysTimeBeforeSending, - context.softwareTimestamp().value()); - - // We cannot make any assertion about hardware - // timestamp availability as it strictly OS+NIC - // dependent. - } - } -#else - // Ensure that no timestamp was generated for other platforms - // except Linux. - - NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); - NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); -#endif - } - - // Now switch off the option and check that requested timestamp is - // not available. - - { - error = - ntsu::SocketOptionUtil::setTimestampIncomingData(client, - false); -#if defined(BSLS_PLATFORM_OS_LINUX) - NTSCFG_TEST_OK(error); -#else - NTSCFG_TEST_ERROR(error, ntsa::Error::e_NOT_IMPLEMENTED); -#endif - - // Enqueue outgoing data to transmit by the client socket. - - { - char buffer = 'S'; - ntsa::SendContext context; - ntsa::SendOptions options; - - ntsa::Data data(ntsa::ConstBuffer(&buffer, 1)); - - sysTimeBeforeSending = bdlt::CurrentTime::now(); - - error = ntsu::SocketUtil::send(&context, - data, - options, - server); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); - NTSCFG_TEST_ASSERT(context.bytesSent() == 1); - } - - // Dequeue incoming data received by the server socket. - - { - char buffer; - ntsa::ReceiveContext context; - ntsa::ReceiveOptions options; - options.showTimestamp(); - - ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); - - error = ntsu::SocketUtil::receive(&context, - &data, - options, - client); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); - NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); - NTSCFG_TEST_ASSERT(buffer == 'S'); - NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); - NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); - } - } - } - - // Validate TX timestamping functionality. - - if (transport != ntsa::Transport::e_TCP_IPV6_STREAM) { - error = - ntsu::SocketOptionUtil::setTimestampOutgoingData(server, true); - bool timestampsAreEnabled = false; -#if defined(BSLS_PLATFORM_OS_LINUX) - if (!ntscfg::Platform::supportsTimestamps()) { - NTSCFG_TEST_ERROR(error, ntsa::Error::e_INVALID); - } - else if (transport == ntsa::Transport::e_LOCAL_STREAM) { - NTSCFG_TEST_ERROR(error, ntsa::Error::e_INVALID); - } - else { - timestampsAreEnabled = true; - NTSCFG_TEST_OK(error); - } + // sleep for 100 ms to let the kernel apply changes + bslmt::ThreadUtil::microSleep(100000, 0); #else NTSCFG_TEST_ERROR(error, ntsa::Error::e_NOT_IMPLEMENTED); #endif @@ -6458,52 +6957,14 @@ NTSCFG_TEST_CASE(17) ntsu::SocketUtil::send(&context, data, options, server); NTSCFG_TEST_ASSERT(!error); - NTSCFG_TEST_EQ(context.bytesSendable(), 1); - NTSCFG_TEST_EQ(context.bytesSent(), 1); - } - - if (timestampsAreEnabled && test::supportsTxTimestamps(server)) { - bslma::TestAllocator ta; - { - ntsa::NotificationQueue notifications(&ta); - error = - ntsu::SocketUtil::receiveNotifications(¬ifications, - server); - NTSCFG_TEST_OK(error); - const int numTimestamps = 3; - NTSCFG_TEST_EQ(notifications.notifications().size(), - numTimestamps); - - bsl::set - timestamps(&ta); - for (int i = 0; i < numTimestamps; ++i) { - NTSCFG_TEST_TRUE( - notifications.notifications().at(i).isTimestamp()); - timestamps.insert( - notifications.notifications().at(i).timestamp()); - } - - BSLS_LOG_DEBUG("Detected TX timestamp"); - - NTSCFG_TEST_EQ(timestamps.size(), 3); - bsl::set::const_iterator - it = timestamps.begin(); - NTSCFG_TEST_EQ(it->type(), - ntsa::TimestampType::e_SCHEDULED); - ++it; - NTSCFG_TEST_EQ(it->type(), ntsa::TimestampType::e_SENT); - ++it; - NTSCFG_TEST_EQ(it->type(), - ntsa::TimestampType::e_ACKNOWLEDGED); - } - NTSCFG_TEST_EQ(ta.numBlocksInUse(), 0); + NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); + NTSCFG_TEST_ASSERT(context.bytesSent() == 1); } // Dequeue incoming data received by the server socket. { - char buffer = 'S'; + char buffer; ntsa::ReceiveContext context; ntsa::ReceiveOptions options; options.showTimestamp(); @@ -6519,26 +6980,61 @@ NTSCFG_TEST_CASE(17) NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); NTSCFG_TEST_ASSERT(buffer == 'S'); +#if defined(BSLS_PLATFORM__OS_LINUX) + int major, minor, patch, build; + NTSCFG_TEST_ASSERT(ntsscm::Version::systemVersion(&major, + &minor, + &patch, + &build) == + 0); + + // Linux kernels versions <= 3.10.0 have restricted + // timestamping support. + + if (KERNEL_VERSION(major, minor, patch) <= + KERNEL_VERSION(3, 10, 0)) + { + NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); + NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); + } + else { + // On a modern kernel timestamps will be present for + // AF_INET stream sockets only. + + if (transport == ntsa::Transport::e_LOCAL_STREAM) { + NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); + NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); + } + else { + BSLS_LOG_DEBUG("Detected RX timestamp"); + NTSCFG_TEST_TRUE( + context.softwareTimestamp().has_value()); + NTSCFG_TEST_LE(sysTimeBeforeSending, + context.softwareTimestamp().value()); + + // We cannot make any assertion about hardware + // timestamp availability as it strictly OS+NIC + // dependent. + } + } +#else + // Ensure that no timestamp was generated for other platforms + // except Linux. + + NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); + NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); +#endif } // Now switch off the option and check that requested timestamp is - // not available on a local stream socket ::recvmsg(socket, &msg, - // MSG_ERRQUEUE); hangs. + // not available. - if (transport != ntsa::Transport::e_LOCAL_STREAM) { + { error = - ntsu::SocketOptionUtil::setTimestampOutgoingData(server, + ntsu::SocketOptionUtil::setTimestampIncomingData(client, false); #if defined(BSLS_PLATFORM_OS_LINUX) - if (!ntscfg::Platform::supportsTimestamps()) { - NTSCFG_TEST_ERROR(error, ntsa::Error::e_INVALID); - } - else if (transport == ntsa::Transport::e_LOCAL_STREAM) { - NTSCFG_TEST_ERROR(error, ntsa::Error::e_INVALID); - } - else { - NTSCFG_TEST_OK(error); - } + NTSCFG_TEST_OK(error); #else NTSCFG_TEST_ERROR(error, ntsa::Error::e_NOT_IMPLEMENTED); #endif @@ -6586,28 +7082,6 @@ NTSCFG_TEST_CASE(17) NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); } - - // Check that no data on the error queue. - - if (test::supportsTxTimestamps(server)) { - bslma::TestAllocator ta; - { - ntsa::NotificationQueue notifications(&ta); - error = ntsu::SocketUtil::receiveNotifications( - ¬ifications, - server); - -#if defined(BSLS_PLATFORM_OS_LINUX) - NTSCFG_TEST_OK(error); - NTSCFG_TEST_EQ(notifications.notifications().size(), - 0); -#else - NTSCFG_TEST_ERROR(error, - ntsa::Error::e_NOT_IMPLEMENTED); -#endif - } - NTSCFG_TEST_EQ(ta.numBlocksInUse(), 0); - } } } @@ -6818,207 +7292,46 @@ NTSCFG_TEST_CASE(18) NTSCFG_TEST_ASSERT(context.endpoint().value() == serverEndpoint); } - else if (transport == ntsa::Transport::e_UDP_IPV6_DATAGRAM) { - NTSCFG_TEST_TRUE( - context.endpoint() - .value() - .ip() - .host() - .v6() - .equalsScopeless(serverEndpoint.ip().host().v6())); - NTSCFG_TEST_TRUE(context.endpoint().value().ip().port() == - serverEndpoint.ip().port()); - } -#if NTSCFG_BUILD_WITH_TRANSPORT_PROTOCOL_LOCAL - else if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { - // Local datagram sockets created with POSIX 'socketpair' - // are connected but are unnamed on some platforms. Those - // platforms report the sender address as "undefined". - NTSCFG_TEST_TRUE( - (serverEndpoint.isImplicit() && - context.endpoint().value().isImplicit()) || - (context.endpoint().value() == serverEndpoint)); - } -#endif - else { - NTSCFG_TEST_TRUE(false); - } - - NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); - NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); - NTSCFG_TEST_ASSERT(buffer == 'C'); - } - - // Test RX timestamping functionality. - - { - error = - ntsu::SocketOptionUtil::setTimestampIncomingData(server, true); -#if defined(BSLS_PLATFORM_OS_LINUX) - NTSCFG_TEST_OK(error); - // sleep for 100 ms to let the kernel apply changes - bslmt::ThreadUtil::microSleep(100000, 0); -#else - NTSCFG_TEST_ERROR(error, ntsa::Error::e_NOT_IMPLEMENTED); -#endif - - // Enqueue outgoing data to transmit by the client socket. - - bsls::TimeInterval sysTimeBeforeSending; - { - char buffer = 'C'; - ntsa::SendContext context; - ntsa::SendOptions options; - - ntsa::Data data(ntsa::ConstBuffer(&buffer, 1)); - - sysTimeBeforeSending = bdlt::CurrentTime::now(); - - error = - ntsu::SocketUtil::send(&context, data, options, client); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); - NTSCFG_TEST_ASSERT(context.bytesSent() == 1); - } - - // Dequeue incoming data received by the server socket. - - { - char buffer; - ntsa::ReceiveContext context; - ntsa::ReceiveOptions options; - options.showTimestamp(); - - ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); - - error = ntsu::SocketUtil::receive(&context, - &data, - options, - server); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); - NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); - NTSCFG_TEST_ASSERT(buffer == 'C'); - -#if defined(BSLS_PLATFORM_OS_LINUX) - int major, minor, patch, build; - NTSCFG_TEST_ASSERT(ntsscm::Version::systemVersion(&major, - &minor, - &patch, - &build) == - 0); - - // Linux kernels versions <= 2.6.32 have restricted - // timestamping support. - - if (KERNEL_VERSION(major, minor, patch) > - KERNEL_VERSION(2, 6, 32)) - { - NTSCFG_TEST_FALSE(context.softwareTimestamp().isNull()); - NTSCFG_TEST_LE(sysTimeBeforeSending, - context.softwareTimestamp().value()); - if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { - NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); - } - } - else { - if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { - NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); - NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); - } - else { - BSLS_LOG_DEBUG("Detected RX timestamp"); - NTSCFG_TEST_FALSE( - context.softwareTimestamp().isNull()); - NTSCFG_TEST_LE(sysTimeBeforeSending, - context.softwareTimestamp().value()); - } - } -#else - NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); - NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); -#endif - } - - // Now switch off the option and check that it is impossible to get - // a timestamp. - - { - error = - ntsu::SocketOptionUtil::setTimestampIncomingData(server, - false); -#if defined(BSLS_PLATFORM_OS_LINUX) - NTSCFG_TEST_OK(error); -#else - NTSCFG_TEST_ERROR(error, ntsa::Error::e_NOT_IMPLEMENTED); -#endif - - // Enqueue outgoing data to transmit by the client socket. - - { - char buffer = 'C'; - ntsa::SendContext context; - ntsa::SendOptions options; - - ntsa::Data data(ntsa::ConstBuffer(&buffer, 1)); - - sysTimeBeforeSending = bdlt::CurrentTime::now(); - - error = ntsu::SocketUtil::send(&context, - data, - options, - client); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); - NTSCFG_TEST_ASSERT(context.bytesSent() == 1); - } - - // Dequeue incoming data received by the server socket. - - { - char buffer; - ntsa::ReceiveContext context; - ntsa::ReceiveOptions options; - options.showTimestamp(); - - ntsa::Data data(ntsa::MutableBuffer(&buffer, 1)); - - error = ntsu::SocketUtil::receive(&context, - &data, - options, - server); - NTSCFG_TEST_ASSERT(!error); - - NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); - NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); - NTSCFG_TEST_ASSERT(buffer == 'C'); - - NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); - NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); - } - } - } - - // Validate TX timestamping functionality. - - if (transport != ntsa::Transport::e_UDP_IPV6_DATAGRAM) { - error = - ntsu::SocketOptionUtil::setTimestampOutgoingData(server, true); - bool timestampsAreEnabled = false; -#if defined(BSLS_PLATFORM_OS_LINUX) - if (!ntscfg::Platform::supportsTimestamps()) { - NTSCFG_TEST_ERROR(error, ntsa::Error::e_INVALID); + else if (transport == ntsa::Transport::e_UDP_IPV6_DATAGRAM) { + NTSCFG_TEST_TRUE( + context.endpoint() + .value() + .ip() + .host() + .v6() + .equalsScopeless(serverEndpoint.ip().host().v6())); + NTSCFG_TEST_TRUE(context.endpoint().value().ip().port() == + serverEndpoint.ip().port()); } +#if NTSCFG_BUILD_WITH_TRANSPORT_PROTOCOL_LOCAL else if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { - NTSCFG_TEST_ERROR(error, ntsa::Error::e_INVALID); + // Local datagram sockets created with POSIX 'socketpair' + // are connected but are unnamed on some platforms. Those + // platforms report the sender address as "undefined". + NTSCFG_TEST_TRUE( + (serverEndpoint.isImplicit() && + context.endpoint().value().isImplicit()) || + (context.endpoint().value() == serverEndpoint)); } +#endif else { - timestampsAreEnabled = true; - NTSCFG_TEST_OK(error); + NTSCFG_TEST_TRUE(false); } + + NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); + NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); + NTSCFG_TEST_ASSERT(buffer == 'C'); + } + + // Test RX timestamping functionality. + + { + error = + ntsu::SocketOptionUtil::setTimestampIncomingData(server, true); +#if defined(BSLS_PLATFORM_OS_LINUX) + NTSCFG_TEST_OK(error); + // sleep for 100 ms to let the kernel apply changes + bslmt::ThreadUtil::microSleep(100000, 0); #else NTSCFG_TEST_ERROR(error, ntsa::Error::e_NOT_IMPLEMENTED); #endif @@ -7036,17 +7349,17 @@ NTSCFG_TEST_CASE(18) sysTimeBeforeSending = bdlt::CurrentTime::now(); error = - ntsu::SocketUtil::send(&context, data, options, server); + ntsu::SocketUtil::send(&context, data, options, client); NTSCFG_TEST_ASSERT(!error); - NTSCFG_TEST_EQ(context.bytesSendable(), 1); - NTSCFG_TEST_EQ(context.bytesSent(), 1); + NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); + NTSCFG_TEST_ASSERT(context.bytesSent() == 1); } // Dequeue incoming data received by the server socket. { - char buffer = 'C'; + char buffer; ntsa::ReceiveContext context; ntsa::ReceiveOptions options; options.showTimestamp(); @@ -7056,66 +7369,62 @@ NTSCFG_TEST_CASE(18) error = ntsu::SocketUtil::receive(&context, &data, options, - client); + server); NTSCFG_TEST_ASSERT(!error); NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); NTSCFG_TEST_ASSERT(buffer == 'C'); - } - if (timestampsAreEnabled && test::supportsTxTimestamps(server)) { - bslma::TestAllocator ta; - { - ntsa::NotificationQueue notifications(&ta); - error = - ntsu::SocketUtil::receiveNotifications(¬ifications, - server); - NTSCFG_TEST_OK(error); - const int numTimestamps = 2; - NTSCFG_TEST_EQ(notifications.notifications().size(), - numTimestamps); - - bsl::set - timestamps(&ta); - for (int i = 0; i < numTimestamps; ++i) { - NTSCFG_TEST_TRUE( - notifications.notifications().at(i).isTimestamp()); - timestamps.insert( - notifications.notifications().at(i).timestamp()); +#if defined(BSLS_PLATFORM_OS_LINUX) + int major, minor, patch, build; + NTSCFG_TEST_ASSERT(ntsscm::Version::systemVersion(&major, + &minor, + &patch, + &build) == + 0); + + // Linux kernels versions <= 2.6.32 have restricted + // timestamping support. - BSLS_LOG_DEBUG("Detected TX timestamp"); + if (KERNEL_VERSION(major, minor, patch) > + KERNEL_VERSION(2, 6, 32)) + { + NTSCFG_TEST_FALSE(context.softwareTimestamp().isNull()); + NTSCFG_TEST_LE(sysTimeBeforeSending, + context.softwareTimestamp().value()); + if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { + NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); + } + } + else { + if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { + NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); + NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); + } + else { + BSLS_LOG_DEBUG("Detected RX timestamp"); + NTSCFG_TEST_FALSE( + context.softwareTimestamp().isNull()); + NTSCFG_TEST_LE(sysTimeBeforeSending, + context.softwareTimestamp().value()); } - NTSCFG_TEST_EQ(timestamps.size(), numTimestamps); - bsl::set::const_iterator - it = timestamps.begin(); - NTSCFG_TEST_EQ(it->type(), - ntsa::TimestampType::e_SCHEDULED); - ++it; - NTSCFG_TEST_EQ(it->type(), ntsa::TimestampType::e_SENT); - } - NTSCFG_TEST_EQ(ta.numBlocksInUse(), 0); + } +#else + NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); + NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); +#endif } - // Now switch off the option and check that requested timestamp is - // not available on a local dgram socket ::recvmsg(socket, &msg, - // MSG_ERRQUEUE); hangs. + // Now switch off the option and check that it is impossible to get + // a timestamp. - if (transport != ntsa::Transport::e_LOCAL_DATAGRAM) { + { error = - ntsu::SocketOptionUtil::setTimestampOutgoingData(server, + ntsu::SocketOptionUtil::setTimestampIncomingData(server, false); #if defined(BSLS_PLATFORM_OS_LINUX) - if (!ntscfg::Platform::supportsTimestamps()) { - NTSCFG_TEST_ERROR(error, ntsa::Error::e_INVALID); - } - else if (transport == ntsa::Transport::e_LOCAL_DATAGRAM) { - NTSCFG_TEST_ERROR(error, ntsa::Error::e_INVALID); - } - else { - NTSCFG_TEST_OK(error); - } + NTSCFG_TEST_OK(error); #else NTSCFG_TEST_ERROR(error, ntsa::Error::e_NOT_IMPLEMENTED); #endif @@ -7134,7 +7443,7 @@ NTSCFG_TEST_CASE(18) error = ntsu::SocketUtil::send(&context, data, options, - server); + client); NTSCFG_TEST_ASSERT(!error); NTSCFG_TEST_ASSERT(context.bytesSendable() == 1); @@ -7154,37 +7463,16 @@ NTSCFG_TEST_CASE(18) error = ntsu::SocketUtil::receive(&context, &data, options, - client); + server); NTSCFG_TEST_ASSERT(!error); NTSCFG_TEST_ASSERT(context.bytesReceivable() == 1); NTSCFG_TEST_ASSERT(context.bytesReceived() == 1); NTSCFG_TEST_ASSERT(buffer == 'C'); + NTSCFG_TEST_TRUE(context.softwareTimestamp().isNull()); NTSCFG_TEST_TRUE(context.hardwareTimestamp().isNull()); } - - // Check that no data on the error queue. - - if (test::supportsTxTimestamps(server)) { - bslma::TestAllocator ta; - { - ntsa::NotificationQueue notifications(&ta); - error = ntsu::SocketUtil::receiveNotifications( - ¬ifications, - server); - -#if defined(BSLS_PLATFORM_OS_LINUX) - NTSCFG_TEST_OK(error); - NTSCFG_TEST_EQ(notifications.notifications().size(), - 0); -#else - NTSCFG_TEST_ERROR(error, - ntsa::Error::e_NOT_IMPLEMENTED); -#endif - } - NTSCFG_TEST_EQ(ta.numBlocksInUse(), 0); - } } } @@ -7413,7 +7701,7 @@ NTSCFG_TEST_CASE(26) { ntsa::Handle socket = ntsa::k_INVALID_HANDLE; - error = ntsu::SocketUtil::create(&socket, + error = ntsu::SocketUtil::create(&socket, ntsa::Transport::e_TCP_IPV4_STREAM); NTSCFG_TEST_OK(error); @@ -7428,6 +7716,276 @@ NTSCFG_TEST_CASE(26) } } +NTSCFG_TEST_CASE(27) +{ + // Concern: Test that Linux MSG_ZEROCOPY mechanism is applied for DATAGRAM + // sockets + + // Note that on that level we cannot really validate whether data is + // actually copied into the send buffer or not. We can only validate that + // if data is sent with MSG_ZEROCOPY flag then related notifications will + // appear on a socket error queue. + // + // By default, the test sends data to a loopback address using loopback + // device. Though, to see how the system behaves when another device is + // used it is possible to use some random (but reachable) IPv4/6 addresses. + // See related code section below. + +#if defined(BSLS_PLATFORM_OS_LINUX) + // Linux kernels versions < 5.0.0 do not support MSG_ZEROCOPY for DGRAM + // sockets + { + int major, minor, patch, build; + NTSCFG_TEST_ASSERT( + ntsscm::Version::systemVersion(&major, &minor, &patch, &build) == + 0); + + if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(5, 0, 0)) { + return; + } + } + + bsl::vector socketTypes; + if (ntsu::AdapterUtil::supportsTransport( + ntsa::Transport::e_UDP_IPV4_DATAGRAM)) + { + socketTypes.push_back(ntsa::Transport::e_UDP_IPV4_DATAGRAM); + } + if (ntsu::AdapterUtil::supportsTransport( + ntsa::Transport::e_UDP_IPV6_DATAGRAM)) + { + socketTypes.push_back(ntsa::Transport::e_UDP_IPV6_DATAGRAM); + } + + for (bsl::vector::const_iterator transport = + socketTypes.cbegin(); + transport != socketTypes.cend(); + ++transport) + { + NTSCFG_TEST_LOG_DEBUG << "Testing " << *transport + << NTSCFG_TEST_LOG_END; + + ntscfg::TestAllocator ta; + { + // Observation: if system MTU is 1500 bytes then maximum payload + // size of UDP IPV4 packet for which MSG_ZEROCOPY functionality can + // really work is 1472 bytes (because UDP header is 8 bytes and + // IPV4 header is 20 bytes). + + const int msgSize = 1472; + const int numMessagesToSend = 200; + + ntsa::Error error; + ntsa::Handle handle = ntsa::k_INVALID_HANDLE; + + error = ntsu::SocketUtil::create(&handle, *transport); + NTSCFG_TEST_ASSERT(!error); + + error = ntsu::SocketOptionUtil::setZeroCopy(handle, true); + NTSCFG_TEST_OK(error); + + bsl::vector message(msgSize, &ta); + for (int i = 0; i < msgSize; ++i) { + message[i] = bsl::rand() % 100; + } + const ntsa::Data data( + ntsa::ConstBuffer(message.data(), message.size())); + + ntsa::Endpoint endpoint; + if (*transport == ntsa::Transport::e_UDP_IPV4_DATAGRAM) { + NTSCFG_TEST_TRUE(endpoint.parse("127.0.0.1:5555")); + // NTSCFG_TEST_TRUE(endpoint.parse("108.22.44.23:5555")); + } + else if (*transport == ntsa::Transport::e_UDP_IPV6_DATAGRAM) { + NTSCFG_TEST_TRUE(endpoint.parse("[::1]:5555")); + // NTSCFG_TEST_TRUE(endpoint.parse("[fe80::215:5dff:fe8d:6bd1]:5555")); + } + + bsl::list feedback(&ta); + bsl::unordered_set sendIDs(&ta); + + for (int i = 0; i < numMessagesToSend; ++i) { + ntsa::SendContext context; + ntsa::SendOptions options; + options.setEndpoint(endpoint); + options.setZeroCopy(true); + + error = + ntsu::SocketUtil::send(&context, data, options, handle); + if (error == ntsa::Error(ntsa::Error::e_WOULD_BLOCK) || + error == ntsa::Error(ntsa::Error::e_LIMIT)) + { + --i; + continue; + } + NTSCFG_TEST_OK(error); + sendIDs.insert(i); + + NTSCFG_TEST_ASSERT(context.bytesSendable() == msgSize); + NTSCFG_TEST_ASSERT(context.bytesSent() == msgSize); + + test::extractZeroCopyNotifications(&feedback, handle, &ta); + } + + // retrieve data from the socket error queue until all send system + // calls are acknowledged by the OS + while (!sendIDs.empty()) { + test::extractZeroCopyNotifications(&feedback, handle, &ta); + + while (!feedback.empty()) { + const ntsa::ZeroCopy& zc = feedback.front(); + if (zc.from() == zc.to()) { + NTSCFG_TEST_EQ(sendIDs.erase(zc.from()), 1); + } + else { + for (bsl::uint32_t i = zc.from(); i != (zc.to() + 1); + ++i) + { + NTSCFG_TEST_EQ(sendIDs.erase(i), 1); + } + } + feedback.pop_front(); + } + } + } + NTSCFG_TEST_ASSERT(ta.numBlocksInUse() == 0); + } +#endif +} + +NTSCFG_TEST_CASE(28) +{ + // Concern: Test that Linux MSG_ZEROCOPY mechanism is applied for STREAM + // sockets + + // Note that on that level we cannot really validate whether data is + // actually copied into the send buffer or not. We can only validate that + // if data is sent with MSG_ZEROCOPY flag then related notifications will + // appear on a socket error queue. + +#if defined(BSLS_PLATFORM_OS_LINUX) + // Linux kernels versions < 4.14.0 do not support MSG_ZEROCOPY for STREAM + // sockets + { + int major, minor, patch, build; + NTSCFG_TEST_ASSERT( + ntsscm::Version::systemVersion(&major, &minor, &patch, &build) == + 0); + + if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(4, 14, 0)) { + return; + } + } + ntscfg::TestAllocator ta; + { + test::executeStreamSocketTest(&test::testStreamSocketMsgZeroCopy); + } + NTSCFG_TEST_ASSERT(ta.numBlocksInUse() == 0); +#endif +} + +NTSCFG_TEST_CASE(29) +{ + // Concern: Test TX timestamping functionality for DATAGRAM sockets +#if defined(BSLS_PLATFORM_OS_LINUX) + // Linux kernels versions < 5.0.0 do not support MSG_ZEROCOPY for DGRAM + // sockets + { + int major, minor, patch, build; + NTSCFG_TEST_ASSERT( + ntsscm::Version::systemVersion(&major, &minor, &patch, &build) == + 0); + + if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(5, 0, 0)) { + return; + } + } + ntscfg::TestAllocator ta; + { + test::executeDatagramSocketTest(&test::testDatagramSocketTxTimestamps); + } + NTSCFG_TEST_ASSERT(ta.numBlocksInUse() == 0); +#endif +} + +NTSCFG_TEST_CASE(30) +{ + // Concern: Test TX timestamping functionality for STREAM sockets +#if defined(BSLS_PLATFORM_OS_LINUX) + // Linux kernels versions < 4.14.0 do not support MSG_ZEROCOPY for STREAM + // sockets + { + int major, minor, patch, build; + NTSCFG_TEST_ASSERT( + ntsscm::Version::systemVersion(&major, &minor, &patch, &build) == + 0); + + if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(4, 14, 0)) { + return; + } + } + ntscfg::TestAllocator ta; + { + test::executeStreamSocketTest(&test::testStreamSocketTxTimestamps); + } + NTSCFG_TEST_ASSERT(ta.numBlocksInUse() == 0); +#endif +} + +NTSCFG_TEST_CASE(31) +{ + // Concern: Test TX timestamping an MSG_ZEROCOPY functionality for + // DATAGRAM sockets +#if defined(BSLS_PLATFORM_OS_LINUX) + + // Linux kernels versions < 5.0.0 do not support MSG_ZEROCOPY for DGRAM + // sockets + { + int major, minor, patch, build; + NTSCFG_TEST_ASSERT( + ntsscm::Version::systemVersion(&major, &minor, &patch, &build) == + 0); + + if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(5, 0, 0)) { + return; + } + } + + ntscfg::TestAllocator ta; + { + test::executeDatagramSocketTest( + &test::testDatagramSocketTxTimestampsAndZeroCopy); + } + NTSCFG_TEST_ASSERT(ta.numBlocksInUse() == 0); +#endif +} + +NTSCFG_TEST_CASE(32) +{ + // Concern: Test TX timestamping an MSG_ZEROCOPY functionality for + // STREAM sockets +#if defined(BSLS_PLATFORM_OS_LINUX) + // Linux kernels versions < 4.14.0 do not support MSG_ZEROCOPY for STREAM + // sockets + { + int major, minor, patch, build; + NTSCFG_TEST_ASSERT( + ntsscm::Version::systemVersion(&major, &minor, &patch, &build) == + 0); + + if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(4, 14, 0)) { + return; + } + } + ntscfg::TestAllocator ta; + { + test::executeStreamSocketTest( + &test::testStreamSocketTxTimestampsAndZeroCopy); + } + NTSCFG_TEST_ASSERT(ta.numBlocksInUse() == 0); +#endif +} + NTSCFG_TEST_DRIVER { NTSCFG_TEST_REGISTER(1); @@ -7456,5 +8014,11 @@ NTSCFG_TEST_DRIVER NTSCFG_TEST_REGISTER(24); NTSCFG_TEST_REGISTER(25); NTSCFG_TEST_REGISTER(26); + NTSCFG_TEST_REGISTER(27); + NTSCFG_TEST_REGISTER(28); + NTSCFG_TEST_REGISTER(29); + NTSCFG_TEST_REGISTER(30); + NTSCFG_TEST_REGISTER(31); + NTSCFG_TEST_REGISTER(32); } NTSCFG_TEST_DRIVER_END; diff --git a/groups/nts/ntsu/ntsu_timestamputil.cpp b/groups/nts/ntsu/ntsu_timestamputil.cpp index e91e2773..562cce59 100644 --- a/groups/nts/ntsu/ntsu_timestamputil.cpp +++ b/groups/nts/ntsu/ntsu_timestamputil.cpp @@ -13,23 +13,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ntsu_timestamputil.h" +#include #include #include +// clang-format off #if defined(BSLS_PLATFORM_OS_LINUX) -//keep this include separate from below includes -//as it must precede errqueue.h inclusion -#include -#endif - -#if defined(BSLS_PLATFORM_OS_LINUX) -#include +#include +#include #include #include #include #endif +// clang-format on namespace BloombergLP { namespace ntsu { diff --git a/groups/nts/ntsu/ntsu_zerocopyutil.cpp b/groups/nts/ntsu/ntsu_zerocopyutil.cpp new file mode 100644 index 00000000..bd12e99f --- /dev/null +++ b/groups/nts/ntsu/ntsu_zerocopyutil.cpp @@ -0,0 +1,51 @@ +// Copyright 2023 Bloomberg Finance L.P. +// SPDX-License-Identifier: Apache-2.0 +// +// 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. + +#include + +#include +#include + +// clang-format off +#if defined(BSLS_PLATFORM_OS_LINUX) +#include +#include +#include +#include +#endif +// clang-format on + +namespace BloombergLP { +namespace ntsu { + +#if defined(BSLS_PLATFORM_OS_LINUX) + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) + +BSLMF_ASSERT(ZeroCopyUtil::e_SO_ZEROCOPY == static_cast(SO_ZEROCOPY)); + +BSLMF_ASSERT(ZeroCopyUtil::e_MSG_ZEROCOPY == + static_cast(MSG_ZEROCOPY)); + +BSLMF_ASSERT(ZeroCopyUtil::e_SO_EE_ORIGIN_ZEROCOPY == + static_cast(SO_EE_ORIGIN_ZEROCOPY)); +BSLMF_ASSERT(ZeroCopyUtil::e_SO_EE_CODE_ZEROCOPY_COPIED == + static_cast(SO_EE_CODE_ZEROCOPY_COPIED)); + +#endif +#endif + +} // close package namespace +} // close enterprise namespace diff --git a/groups/nts/ntsu/ntsu_zerocopyutil.h b/groups/nts/ntsu/ntsu_zerocopyutil.h new file mode 100644 index 00000000..2b29bca6 --- /dev/null +++ b/groups/nts/ntsu/ntsu_zerocopyutil.h @@ -0,0 +1,45 @@ +// Copyright 2023 Bloomberg Finance L.P. +// SPDX-License-Identifier: Apache-2.0 +// +// 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 INCLUDED_NTSU_ZEROCOPYUTIL +#define INCLUDED_NTSU_ZEROCOPYUTIL + +#include +BSLS_IDENT("$Id: $") + +namespace BloombergLP { +namespace ntsu { + +/// This struct is used to define/redefine types and constants used for Linux +/// msg zerocopy feature. +struct ZeroCopyUtil { + enum { //copied from include/asm-generic/socket.h + e_SO_ZEROCOPY = 60 + }; + + enum { // copied from include/linux/socket.h + e_MSG_ZEROCOPY = 0x4000000 + }; + + enum { // copied from include/linux/errqueue.h + e_SO_EE_ORIGIN_ZEROCOPY = 5, + e_SO_EE_CODE_ZEROCOPY_COPIED = 1 + }; +}; + +} // close package namespace +} // close enterprise namespace + +#endif diff --git a/groups/nts/ntsu/package/ntsu.mem b/groups/nts/ntsu/package/ntsu.mem index e2763537..36d053a0 100644 --- a/groups/nts/ntsu/package/ntsu.mem +++ b/groups/nts/ntsu/package/ntsu.mem @@ -1,5 +1,6 @@ ntsu_adapterutil ntsu_bufferutil +ntsu_zerocopyutil ntsu_resolverutil ntsu_socketutil ntsu_socketoptionutil diff --git a/targets.cmake b/targets.cmake index 2beafd67..e736c819 100644 --- a/targets.cmake +++ b/targets.cmake @@ -158,6 +158,7 @@ if (${NTF_BUILD_WITH_NTS}) ntf_component(NAME ntsa_timestamp) ntf_component(NAME ntsa_timestamptype) ntf_component(NAME ntsa_uri) + ntf_component(NAME ntsa_zerocopy) ntf_package_end(NAME ntsa) @@ -213,11 +214,13 @@ if (${NTF_BUILD_WITH_NTS}) ntf_component(NAME ntsu_adapterutil) ntf_component(NAME ntsu_bufferutil) + ntf_component(NAME ntsu_zerocopyutil) ntf_component(NAME ntsu_resolverutil) ntf_component(NAME ntsu_socketutil) ntf_component(NAME ntsu_socketoptionutil) ntf_component(NAME ntsu_timestamputil) + ntf_package_end(NAME ntsu) ntf_package(