using System;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using SBRL.Utilities;
using System.Text;
using System.Runtime.CompilerServices;
namespace PixelServer.Net
{
///
/// Provides a mechanism to broadcast a program's presence on a multicast address and port.
///
public class DiscoveryBeacon : IDisposable
{
TextWriter logger;
UdpClient beacon;
///
/// Whether the beacon is currently active.
/// If true then the beacon needs disposing of properly by calling the Dispose() method before the program ends.
///
public bool Active { get; private set; } = true;
string role = "server";
///
/// The current IP address of the local machine.
///
///
/// Setting this value affects the IP address that is broadcast by the beacon.
///
public IPAddress LocalIP { get; set; }
///
/// The (multicast) IP address that the beacon should broadcast to.
///
public IPAddress DestinationAddress { get; private set; }
///
/// The multicast endpoint that the beacon will broadcast to.
/// This value is dynamically generated from the DestinationAddress and Port properties.
///
public IPEndPoint MulticastChannel { get; private set; }
///
/// The port that the beacon should broadcast on.
///
public int Port { get; set; } = 5050;
public string Role {
get{
return role;
}
private set {
if (value.Contains(" "))
throw new InvalidDataException($"Error: Invalid role '{value}'.");
role = value;
}
}
///
/// The frequency at which the beacon should emit broadcasts.
///
public int EmitFrequency { get; set; } = 2000;
///
/// Initializes a new instance of the class.
///
/// The destination address to broadcast to.
/// The port to broadcast on.
/// The place to send log messages to defaults to Console.Out.
public DiscoveryBeacon(IPAddress inDestAddress, int inPort, TextWriter inLogger)
{
logger = inLogger;
Port = inPort;
DestinationAddress = inDestAddress;
MulticastChannel = new IPEndPoint(DestinationAddress, Port);
LocalIP = findLocalIP();
logger.WriteLine("Using {0} as the local IP Address.", LocalIP);
}
///
/// Initializes a new instance of the class.
///
/// The destination address to broadcast to.
/// The port to broadcast on.
public DiscoveryBeacon(IPAddress inDestAddress, int inPort) : this(inDestAddress, inPort, Console.Out)
{
}
///
/// Initializes a new instance of the class.
///
/// The destination address to broadcast to.
public DiscoveryBeacon(IPAddress inDestAddress) : this(inDestAddress, 5050)
{
}
///
/// Start the beacon up and broadcast the program's presence on a regular (See basis.
///
public async Task Emit()
{
connect();
while(true)
{
await emitOnce();
await Task.Delay(EmitFrequency);
}
}
///
/// Finds the IP of the machine the beacon is running on.
///
/// The first IP found that points to this machine.
private IPAddress findLocalIP()
{
List localIPS = Utilities.GetLocalIps().ToList();
return localIPS[0];
}
///
/// Connects the underlying UdpClient client.
///
private void connect()
{
beacon = new UdpClient(Port);
Active = true;
beacon.JoinMulticastGroup(DestinationAddress);
}
///
/// Emits a single presence broadcast from the beacon.
///
private async Task emitOnce()
{
await Send($"{Role}@{LocalIP}:{Port}\n");
}
///
/// Sends a message from the underlying UdpClient to the attached multicast address.
///
/// The message to send.
private async Task Send(string data)
{
await Send(MulticastChannel, data);
}
///
/// Sends a message to the given endpoint using the underlying UdpClient.
///
/// The place to send the message to.
/// The message payload.
private async Task Send(IPEndPoint destination, string data)
{
logger.WriteLine("Sending '{0}' to {1}.", Utilities.EscapeString(data), destination);
byte[] payload = Encoding.UTF8.GetBytes(data);
await Send(destination, payload);
}
///
/// Sends a raw byte array message to the given endpoint.
/// This is the lowest level Send() method before the message is passed to the UcpClient itself.
///
/// THe destination endpoint to send the message to.
/// The byte array to use as the message body.
private async Task Send(IPEndPoint destination, byte[] payload)
{
if (beacon == null)
throw new InvalidOperationException("Error: Can't send data until you join a channel.");
await beacon.SendAsync(payload, payload.Length, MulticastChannel);
}
///
/// Releases all resource used by the .
///
///
/// Call when you are finished using the . The
/// method leaves the in an unusable state. After
/// calling , you must release all references to the
/// so the garbage collector can reclaim the memory that the
/// was occupying.
public void Dispose()
{
beacon.Close();
}
}
}