diff --git a/src/GameCult.Networking/CultNetTransport.cs b/src/GameCult.Networking/CultNetTransport.cs
index a6102de3..ac2c2952 100644
--- a/src/GameCult.Networking/CultNetTransport.cs
+++ b/src/GameCult.Networking/CultNetTransport.cs
@@ -1490,15 +1490,30 @@ namespace GameCult.Networking
internal const string UnsendablePacketReason = "packet could not be sent";
+ ///
+ /// The fixed name of a failed send's cause: EMSGSIZE, EINVAL or EAFNOSUPPORT for a failure
+ /// that can never pass (), UNKNOWN for anything else, as
+ /// every runtime's goodbye names it. Never the error's own message, which is the platform's
+ /// wording.
+ ///
+ internal static string SendErrorCode(SocketException error) => error.SocketErrorCode switch
+ {
+ SocketError.MessageSize => "EMSGSIZE",
+ SocketError.InvalidArgument => "EINVAL",
+ SocketError.AddressFamilyNotSupported => "EAFNOSUPPORT",
+ _ => "UNKNOWN",
+ };
+
///
/// Ends a session that owes its peer a packet that can never be sent as built. Resending it
/// would fail forever, and dropping it would leave a reliable sequence the peer waits on for
- /// good, so the session cannot be kept. The goodbye's reason names the error.
+ /// good, so the session cannot be kept. The goodbye's reason names the failure by its fixed
+ /// name ().
///
internal CultNetRudpPacket EndUnsendable(SocketException error)
{
ResetPeerState();
- return CreateDisconnect(Encoding.UTF8.GetBytes(UnsendablePacketReason + ": " + error.Message));
+ return CreateDisconnect(Encoding.UTF8.GetBytes(UnsendablePacketReason + ": " + SendErrorCode(error)));
}
internal uint NextSequence => _nextSequence;
diff --git a/tests/GameCult.Networking.Tests/CultNetRudpSendFailureTests.cs b/tests/GameCult.Networking.Tests/CultNetRudpSendFailureTests.cs
index 81d255f7..3decfa46 100644
--- a/tests/GameCult.Networking.Tests/CultNetRudpSendFailureTests.cs
+++ b/tests/GameCult.Networking.Tests/CultNetRudpSendFailureTests.cs
@@ -137,6 +137,18 @@ namespace GameCult.Networking.Tests
// caller-directed send throws it and queues nothing; inside a poll it ends that peer's
// session and never the poll.
+ [Test]
+ public void An_unsendable_goodbye_names_the_failure_by_a_fixed_code_never_its_message()
+ {
+ var session = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 1 });
+ Assert.That(
+ Encoding.UTF8.GetString(session.EndUnsendable(new SocketException((int)SocketError.MessageSize)).Payload!),
+ Is.EqualTo("packet could not be sent: EMSGSIZE"));
+ Assert.That(CultNetRudpSession.SendErrorCode(new SocketException((int)SocketError.InvalidArgument)), Is.EqualTo("EINVAL"));
+ Assert.That(CultNetRudpSession.SendErrorCode(new SocketException((int)SocketError.AddressFamilyNotSupported)), Is.EqualTo("EAFNOSUPPORT"));
+ Assert.That(CultNetRudpSession.SendErrorCode(new SocketException((int)SocketError.ConnectionRefused)), Is.EqualTo("UNKNOWN"));
+ }
+
private static bool IsUnsendableReason(byte[]? reason)
{
var text = Encoding.UTF8.GetString(reason ?? Array.Empty());