/
UnixDomainSocketEndPoint.cs
170 lines (141 loc) · 7.32 KB
/
UnixDomainSocketEndPoint.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
namespace System.Net.Sockets
{
/// <summary>Represents a Unix Domain Socket endpoint as a path.</summary>
public sealed partial class UnixDomainSocketEndPoint : EndPoint
{
private const AddressFamily EndPointAddressFamily = AddressFamily.Unix;
private readonly string _path;
private readonly byte[] _encodedPath;
// Tracks the file Socket should delete on Dispose.
internal string? BoundFileName { get; }
/// <summary>Initializes a new instance of the <see cref="UnixDomainSocketEndPoint"/> with the file path to connect a unix domain socket over.</summary>
/// <param name="path">The path to connect a unix domain socket over.</param>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="path"/> is of an invalid length for use with domain sockets on this platform. The length must be between 1 and the allowed native path length.</exception>
/// <exception cref="PlatformNotSupportedException">The current OS does not support Unix Domain Sockets.</exception>
public UnixDomainSocketEndPoint(string path)
: this(path, null)
{ }
private UnixDomainSocketEndPoint(string path, string? boundFileName)
{
ArgumentNullException.ThrowIfNull(path);
BoundFileName = boundFileName;
// Pathname socket addresses should be null-terminated.
// Linux abstract socket addresses start with a zero byte, they must not be null-terminated.
bool isAbstract = IsAbstract(path);
int bufferLength = Encoding.UTF8.GetByteCount(path);
if (!isAbstract)
{
// for null terminator
bufferLength++;
}
if (path.Length == 0 || bufferLength > s_nativePathLength)
{
throw new ArgumentOutOfRangeException(
nameof(path), path,
SR.Format(SR.ArgumentOutOfRange_PathLengthInvalid, path, s_nativePathLength));
}
_path = path;
_encodedPath = new byte[bufferLength];
int bytesEncoded = Encoding.UTF8.GetBytes(path, 0, path.Length, _encodedPath, 0);
Debug.Assert(bufferLength - (isAbstract ? 0 : 1) == bytesEncoded);
if (!Socket.OSSupportsUnixDomainSockets)
{
throw new PlatformNotSupportedException();
}
}
internal static int MaxAddressSize => s_nativeAddressSize;
internal UnixDomainSocketEndPoint(ReadOnlySpan<byte> socketAddress)
{
Debug.Assert(AddressFamily.Unix == SocketAddressPal.GetAddressFamily(socketAddress));
if (socketAddress.Length > s_nativePathOffset)
{
_encodedPath = new byte[socketAddress.Length - s_nativePathOffset];
for (int i = 0; i < _encodedPath.Length; i++)
{
_encodedPath[i] = socketAddress[s_nativePathOffset + i];
}
// Strip trailing null of pathname socket addresses.
int length = _encodedPath.Length;
if (!IsAbstract(_encodedPath))
{
// Since this isn't an abstract path, we're sure our first byte isn't 0.
while (_encodedPath[length - 1] == 0)
{
length--;
}
}
_path = Encoding.UTF8.GetString(_encodedPath, 0, length);
}
else
{
_encodedPath = Array.Empty<byte>();
_path = string.Empty;
}
}
/// <summary>Serializes endpoint information into a <see cref="SocketAddress"/> instance.</summary>
/// <returns>A <see cref="SocketAddress"/> instance that contains the endpoint information.</returns>
public override SocketAddress Serialize()
{
SocketAddress result = CreateSocketAddressForSerialize();
for (int index = 0; index < _encodedPath.Length; index++)
{
result[s_nativePathOffset + index] = _encodedPath[index];
}
return result;
}
/// <summary>Creates an <see cref="EndPoint"/> instance from a <see cref="SocketAddress"/> instance.</summary>
/// <param name="socketAddress">The socket address that serves as the endpoint for a connection.</param>
/// <returns>A new <see cref="EndPoint"/> instance that is initialized from the specified <see cref="SocketAddress"/> instance.</returns>
public override EndPoint Create(SocketAddress socketAddress) => new UnixDomainSocketEndPoint(socketAddress.Buffer.Span.Slice(0, socketAddress.Size));
/// <summary>Gets the address family to which the endpoint belongs.</summary>
/// <value>One of the <see cref="AddressFamily"/> values.</value>
public override AddressFamily AddressFamily => EndPointAddressFamily;
/// <summary>Gets the path represented by this <see cref="UnixDomainSocketEndPoint"/> instance.</summary>
/// <returns>The path represented by this <see cref="UnixDomainSocketEndPoint"/> instance.</returns>
public override string ToString()
{
bool isAbstract = IsAbstract(_path);
if (isAbstract)
{
return string.Concat("@", _path.AsSpan(1));
}
else
{
return _path;
}
}
/// <summary>Determines whether the specified <see cref="object"/> is equal to the current <see cref="UnixDomainSocketEndPoint"/>.</summary>
/// <param name="obj">The <see cref="object"/> to compare with the current <see cref="UnixDomainSocketEndPoint"/>.</param>
/// <returns><see langword="true"/> if the specified <see cref="object"/> is equal to the current <see cref="UnixDomainSocketEndPoint"/>; otherwise, <see langword="false"/>.</returns>
public override bool Equals([NotNullWhen(true)] object? obj)
=> obj is UnixDomainSocketEndPoint ep && _path == ep._path;
/// <summary>Returns a hash value for a <see cref="UnixDomainSocketEndPoint"/> instance.</summary>
/// <returns>An integer hash value.</returns>
public override int GetHashCode() => _path.GetHashCode();
internal UnixDomainSocketEndPoint CreateBoundEndPoint()
{
if (IsAbstract(_path))
{
return this;
}
return new UnixDomainSocketEndPoint(_path, Path.GetFullPath(_path));
}
internal UnixDomainSocketEndPoint CreateUnboundEndPoint()
{
if (IsAbstract(_path) || BoundFileName is null)
{
return this;
}
return new UnixDomainSocketEndPoint(_path, null);
}
private static bool IsAbstract(string path) => path.Length > 0 && path[0] == '\0';
private static bool IsAbstract(byte[] encodedPath) => encodedPath.Length > 0 && encodedPath[0] == 0;
}
}