-
Notifications
You must be signed in to change notification settings - Fork 5
/
RadiusClient.cs
103 lines (92 loc) · 4.03 KB
/
RadiusClient.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
using Flexinets.Radius.Core;
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Flexinets.Radius
{
public class RadiusClient : IDisposable
{
private readonly IPEndPoint _localEndpoint;
private readonly UdpClient _udpClient;
private readonly IRadiusPacketParser _radiusPacketParser;
private readonly ConcurrentDictionary<(Byte identifier, IPEndPoint remoteEndpoint), TaskCompletionSource<UdpReceiveResult>> _pendingRequests = new ConcurrentDictionary<(Byte, IPEndPoint), TaskCompletionSource<UdpReceiveResult>>();
/// <summary>
/// Create a radius client which sends and receives responses on localEndpoint
/// </summary>
/// <param name="localEndpoint"></param>
/// <param name="dictionary"></param>
public RadiusClient(IPEndPoint localEndpoint, IRadiusPacketParser radiusPacketParser)
{
_localEndpoint = localEndpoint;
_radiusPacketParser = radiusPacketParser;
_udpClient = new UdpClient(_localEndpoint);
var receiveTask = StartReceiveLoopAsync();
}
/// <summary>
/// Send a packet with specified timeout
/// </summary>
/// <param name="packet"></param>
/// <param name="remoteEndpoint"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<IRadiusPacket> SendPacketAsync(IRadiusPacket packet, IPEndPoint remoteEndpoint, TimeSpan timeout)
{
var packetBytes = _radiusPacketParser.GetBytes(packet);
var responseTaskCS = new TaskCompletionSource<UdpReceiveResult>();
if (_pendingRequests.TryAdd((packet.Identifier, remoteEndpoint), responseTaskCS))
{
await _udpClient.SendAsync(packetBytes, packetBytes.Length, remoteEndpoint);
var completedTask = await Task.WhenAny(responseTaskCS.Task, Task.Delay(timeout));
if (completedTask == responseTaskCS.Task)
{
return _radiusPacketParser.Parse(responseTaskCS.Task.Result.Buffer, packet.SharedSecret);
}
if (_pendingRequests.TryRemove((packet.Identifier, remoteEndpoint), out var taskCS))
{
taskCS.SetCanceled();
}
throw new InvalidOperationException($"Receive response for id {packet.Identifier} timed out after {timeout}");
}
throw new InvalidOperationException($"There is already a pending receive with id {packet.Identifier}");
}
/// <summary>
/// Send a packet with default timeout of 3 seconds
/// </summary>
/// <param name="packet"></param>
/// <param name="remoteEndpoint"></param>
/// <returns></returns>
public async Task<IRadiusPacket> SendPacketAsync(IRadiusPacket packet, IPEndPoint remoteEndpoint)
{
return await SendPacketAsync(packet, remoteEndpoint, TimeSpan.FromSeconds(3));
}
/// <summary>
/// Receive packets in a loop and complete tasks based on identifier
/// </summary>
/// <returns></returns>
private async Task StartReceiveLoopAsync()
{
while (true) // Maybe this should be started and stopped when there are pending responses
{
try
{
var response = await _udpClient.ReceiveAsync();
if (_pendingRequests.TryRemove((response.Buffer[1], response.RemoteEndPoint), out var taskCS))
{
taskCS.SetResult(response);
}
}
catch (ObjectDisposedException)
{
// This is thrown when udpclient is disposed, can be safely ignored
return;
}
}
}
public void Dispose()
{
_udpClient?.Dispose();
}
}
}