From 9ae5d2c97a9492812891a65b87463a49e009a4ec Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Sat, 15 Oct 2016 13:27:19 +0100
Subject: [PATCH] This project is ballooning into a fully fledged thing. It
 needs VCS!

---
 .gitignore                             | 275 +++++++++++++++++++++++++
 PixelServer.sln                        |  17 ++
 PixelServer/DiscoveryBeacon.cs         | 189 +++++++++++++++++
 PixelServer/ForgetTask.cs              |  32 +++
 PixelServer/PixelServer.csproj         |  43 ++++
 PixelServer/Program.cs                 |  22 ++
 PixelServer/Properties/AssemblyInfo.cs |  27 +++
 PixelServer/Utilities.cs               |  37 ++++
 8 files changed, 642 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 PixelServer.sln
 create mode 100644 PixelServer/DiscoveryBeacon.cs
 create mode 100644 PixelServer/ForgetTask.cs
 create mode 100644 PixelServer/PixelServer.csproj
 create mode 100644 PixelServer/Program.cs
 create mode 100644 PixelServer/Properties/AssemblyInfo.cs
 create mode 100644 PixelServer/Utilities.cs

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c565588
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,275 @@
+
+# Created by https://www.gitignore.io/api/visualstudio
+
+### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/
+
+### VisualStudio Patch ###
+build/
diff --git a/PixelServer.sln b/PixelServer.sln
new file mode 100644
index 0000000..fde9661
--- /dev/null
+++ b/PixelServer.sln
@@ -0,0 +1,17 @@
+﻿
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixelServer", "PixelServer\PixelServer.csproj", "{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}.Debug|x86.ActiveCfg = Debug|x86
+		{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}.Debug|x86.Build.0 = Debug|x86
+		{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}.Release|x86.ActiveCfg = Release|x86
+		{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+EndGlobal
diff --git a/PixelServer/DiscoveryBeacon.cs b/PixelServer/DiscoveryBeacon.cs
new file mode 100644
index 0000000..46ea2c7
--- /dev/null
+++ b/PixelServer/DiscoveryBeacon.cs
@@ -0,0 +1,189 @@
+﻿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
+{
+	/// <summary>
+	/// Provides a mechanism to broadcast a program's presence on a multicast address and port.
+	/// </summary>
+	public class DiscoveryBeacon : IDisposable
+	{
+		TextWriter logger;
+		UdpClient beacon;
+		/// <summary>
+		/// Whether the beacon is currently active.
+		/// If true then the beacon needs disposing of properly by calling the Dispose() method before the program ends.
+		/// </summary>
+		public bool Active { get; private set; } = true;
+
+		string role = "server";
+
+		/// <summary>
+		/// The current IP address of the local machine.
+		/// </summary>
+		/// <remarks>
+		/// Setting this value affects the IP address that is broadcast by the beacon.
+		/// </remarks>
+		public IPAddress LocalIP { get; set; }
+		/// <summary>
+		/// The (multicast) IP address that the beacon should broadcast to.
+		/// </summary>
+		public IPAddress DestinationAddress { get; private set; }
+		/// <summary>
+		/// The multicast endpoint that the beacon will broadcast to.
+		/// This value is dynamically generated from the DestinationAddress and Port properties.
+		/// </summary>
+		public IPEndPoint MulticastChannel { get; private set; }
+		/// <summary>
+		/// The port that the beacon should broadcast on.
+		/// </summary>
+		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;
+			}
+		}
+
+		/// <summary>
+		/// The frequency at which the beacon should emit broadcasts.
+		/// </summary>
+		public int EmitFrequency { get; set; } = 2000;
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="PixelServer.Net.DiscoveryBeacon"/> class.
+		/// </summary>
+		/// <param name="inDestAddress">The destination address to broadcast to.</param>
+		/// <param name="inPort">The port to broadcast on.</param>
+		/// <param name="inLogger">The place to send log messages to defaults to Console.Out.</param>
+		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);
+		}
+		/// <summary>
+		/// Initializes a new instance of the <see cref="PixelServer.Net.DiscoveryBeacon"/> class.
+		/// </summary>
+		/// <param name="inDestAddress">The destination address to broadcast to.</param>
+		/// <param name="inPort">The port to broadcast on.</param>
+		public DiscoveryBeacon(IPAddress inDestAddress, int inPort) : this(inDestAddress, inPort, Console.Out)
+		{
+		}
+		/// <summary>
+		/// Initializes a new instance of the <see cref="PixelServer.Net.DiscoveryBeacon"/> class.
+		/// </summary>
+		/// <param name="inDestAddress">The destination address to broadcast to.</param>
+		public DiscoveryBeacon(IPAddress inDestAddress) : this(inDestAddress, 5050)
+		{
+		}
+
+		/// <summary>
+		/// Start the beacon up and broadcast the program's presence on a regular (See <see cref="EmitFrequency"/> basis.
+		/// </summary>
+		public async Task Emit()
+		{
+			connect();
+
+			while(true)
+			{
+				await emitOnce();
+				await Task.Delay(EmitFrequency);
+			}
+		}
+
+		/// <summary>
+		/// Finds the IP of the machine the beacon is running on.
+		/// </summary>
+		/// <returns>The first IP found that points to this machine.</returns>
+		private IPAddress findLocalIP()
+		{
+			List<IPAddress> localIPS = Utilities.GetLocalIps().ToList();
+			return localIPS[0];
+		}
+
+		/// <summary>
+		/// Connects the underlying UdpClient client.
+		/// </summary>
+		private void connect()
+		{
+			beacon = new UdpClient(Port);
+			Active = true;
+			beacon.JoinMulticastGroup(DestinationAddress);
+		}
+		
+		/// <summary>
+		/// Emits a single presence broadcast from the beacon.
+		/// </summary>
+		private async Task emitOnce()
+		{
+			await Send($"{Role}@{LocalIP}:{Port}\n");
+		}
+
+		/// <summary>
+		/// Sends a message from the underlying UdpClient to the attached multicast address.
+		/// </summary>
+		/// <param name="data">The message to send.</param>
+		private async Task Send(string data)
+		{
+			await Send(MulticastChannel, data);
+		}
+		/// <summary>
+		/// Sends a message to the given endpoint using the underlying UdpClient.
+		/// </summary>
+		/// <param name="destination">The place to send the message to.</param>
+		/// <param name="data">The message payload.</param>
+		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);
+		}
+		/// <summary>
+		/// 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.
+		/// </summary>
+		/// <param name="destination">THe destination endpoint to send the message to.</param>
+		/// <param name="payload">The byte array to use as the message body.</param>
+		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);
+		}
+
+		/// <summary>
+		/// Releases all resource used by the <see cref="PixelServer.Net.DiscoveryBeacon"/>.
+		/// </summary>
+		/// <remarks>
+		/// Call <see cref="Dispose"/> when you are finished using the <see cref="PixelServer.Net.DiscoveryBeacon"/>. The
+		/// <see cref="Dispose"/> method leaves the <see cref="PixelServer.Net.DiscoveryBeacon"/> in an unusable state. After
+		/// calling <see cref="Dispose"/>, you must release all references to the
+		/// <see cref="PixelServer.Net.DiscoveryBeacon"/> so the garbage collector can reclaim the memory that the
+		/// <see cref="PixelServer.Net.DiscoveryBeacon"/> was occupying.</remarks>
+		public void Dispose()
+		{
+			beacon.Close();
+		}
+	}
+}
+
diff --git a/PixelServer/ForgetTask.cs b/PixelServer/ForgetTask.cs
new file mode 100644
index 0000000..cbe3531
--- /dev/null
+++ b/PixelServer/ForgetTask.cs
@@ -0,0 +1,32 @@
+﻿using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Linq;
+
+namespace SBRL.Utilities
+{
+	class AsyncTools
+	{
+		/// <summary>
+		/// Call this method to allow a given task to complete in the background.
+		/// Errors will be handled correctly.
+		/// Useful in fire-and-forget scenarios, like a TCP server for example.
+		/// From http://stackoverflow.com/a/22864616/1460422
+		/// </summary>
+		/// <param name="targetTask">The task to forget about.</param>
+		/// <param name="acceptableExceptions">Acceptable exceptions. Exceptions specified here won't cause a crash.</param>
+		public static async void ForgetTask(Task targetTask, params Type[] acceptableExceptions)
+		{
+			try
+			{
+				await targetTask.ConfigureAwait(false);
+			}
+			catch (Exception ex)
+			{
+				// TODO: consider whether derived types are also acceptable.
+				if (!acceptableExceptions.Contains(ex.GetType()))
+					throw;
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/PixelServer/PixelServer.csproj b/PixelServer/PixelServer.csproj
new file mode 100644
index 0000000..66a53b8
--- /dev/null
+++ b/PixelServer/PixelServer.csproj
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProjectGuid>{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>PixelServer</RootNamespace>
+    <AssemblyName>PixelServer</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="DiscoveryBeacon.cs" />
+    <Compile Include="Utilities.cs" />
+    <Compile Include="ForgetTask.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/PixelServer/Program.cs b/PixelServer/Program.cs
new file mode 100644
index 0000000..bf50619
--- /dev/null
+++ b/PixelServer/Program.cs
@@ -0,0 +1,22 @@
+﻿using System;
+
+using PixelServer.Net;
+using System.Net;
+using System.Threading.Tasks;
+
+using SBRL.Utilities;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+
+namespace PixelServer
+{
+	class MainClass
+	{
+		public static void Main(string[] args)
+		{
+			DiscoveryBeacon beacon = new DiscoveryBeacon(IPAddress.Parse("239.62.148.30"));
+			//AsyncTools.ForgetTask(beacon.Emit());
+			beacon.Emit().Wait();
+		}
+	}
+}
diff --git a/PixelServer/Properties/AssemblyInfo.cs b/PixelServer/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f4896bd
--- /dev/null
+++ b/PixelServer/Properties/AssemblyInfo.cs
@@ -0,0 +1,27 @@
+﻿using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("PixelServer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("Starbeamrainbowlabs")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/PixelServer/Utilities.cs b/PixelServer/Utilities.cs
new file mode 100644
index 0000000..0d7d117
--- /dev/null
+++ b/PixelServer/Utilities.cs
@@ -0,0 +1,37 @@
+﻿using System;
+using System.Collections;
+using System.Net;
+using System.Linq;
+using System.Collections.Generic;
+using System.Text;
+using System.Security.Cryptography;
+
+namespace SBRL.Utilities
+{
+	public static class Utilities
+	{
+		public static IEnumerable<IPAddress> GetLocalIps()
+		{
+			IPHostEntry entries = Dns.GetHostEntry(Dns.GetHostName());
+			return (from ipAddr in entries.AddressList
+			        where !IPAddress.IsLoopback(ipAddr)
+			        select ipAddr).ToList();
+		}
+
+		public static string EscapeString(string str)
+		{
+			StringBuilder result = new StringBuilder(str);
+			foreach(KeyValuePair<string, string> kvp in replacementPairs)
+				result.Replace(kvp.Key, kvp.Value);
+
+			return result.ToString();
+		}
+
+		private static Dictionary<string, string> replacementPairs = new Dictionary<string, string>()
+		{
+			["\n"] = @"\n",
+			["\t"] = @"\t"
+		};
+	}
+}
+
