├── .gitignore ├── LICENSE ├── README.md ├── build.bat └── src ├── AckDetail.cs ├── AutonomousTransciever.cs ├── AwaitingAcceptDetail.cs ├── Const.cs ├── Datagram.cs ├── DatagramPool.cs ├── DatagramSocketTransceiver.cs ├── DelayedDatagram.cs ├── Delegates.cs ├── EmitDiscoverySignalTask.cs ├── Enums.cs ├── ExceptionHelper.cs ├── FalconExtensions.cs ├── FalconHelper.cs ├── FalconOperationResult.cs ├── FalconPeer.cs ├── FalconUDP.csproj ├── FalconUDP.sln ├── FalconUDPTests ├── FalconPeerTests.cs ├── FalconUDP.WindowsUniversal.Tests │ ├── Assets │ │ ├── LockScreenLogo.scale-200.png │ │ ├── SplashScreen.scale-200.png │ │ ├── Square150x150Logo.scale-200.png │ │ ├── Square44x44Logo.scale-200.png │ │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ │ ├── StoreLogo.png │ │ └── Wide310x150Logo.scale-200.png │ ├── FalconUDP.WindowsUniversal.Tests.csproj │ ├── FalconUDP.WindowsUniversal.Tests_TemporaryKey.pfx │ ├── Package.appxmanifest │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── UnitTestApp.rd.xml │ ├── UnitTestApp.xaml │ ├── UnitTestApp.xaml.cs │ ├── project.json │ └── project.lock.json ├── FalconUDPTests.csproj ├── PacketTests.cs └── Properties │ └── AssemblyInfo.cs ├── GenericObjectPool.cs ├── IFalconTransceiver.cs ├── Packet.cs ├── PacketPool.cs ├── PingDetail.cs ├── PoolSizes.cs ├── QualityOfService.cs ├── ReceiveChannel.cs ├── RemotePeer.cs ├── SendChannel.cs ├── SingleRandom.cs ├── SocketTransceiver.cs ├── Statistics.cs ├── TransceiverFactory.cs ├── UPnPInternetGatewayDevice.cs └── UWPExtensions.cs /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | 3 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 4 | [Bb]in/ 5 | [Oo]bj/ 6 | 7 | # mstest test results 8 | TestResults 9 | 10 | ## Ignore Visual Studio temporary files, build results, and 11 | ## files generated by popular Visual Studio add-ons. 12 | 13 | # User-specific files 14 | *.suo 15 | *.user 16 | *.sln.docstates 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Rr]elease/ 21 | x64/ 22 | *_i.c 23 | *_p.c 24 | *.ilk 25 | *.meta 26 | *.obj 27 | *.pch 28 | *.pdb 29 | *.pgc 30 | *.pgd 31 | *.rsp 32 | *.sbr 33 | *.tlb 34 | *.tli 35 | *.tlh 36 | *.tmp 37 | *.log 38 | *.vspscc 39 | *.vssscc 40 | .builds 41 | 42 | # Visual C++ cache files 43 | ipch/ 44 | *.aps 45 | *.ncb 46 | *.opensdf 47 | *.sdf 48 | 49 | # Visual Studio profiler 50 | *.psess 51 | *.vsp 52 | *.vspx 53 | 54 | # Guidance Automation Toolkit 55 | *.gpState 56 | 57 | # ReSharper is a .NET coding add-in 58 | _ReSharper* 59 | 60 | # NCrunch 61 | *.ncrunch* 62 | .*crunch*.local.xml 63 | 64 | # Installshield output folder 65 | [Ee]xpress 66 | 67 | # DocProject is a documentation generator add-in 68 | DocProject/buildhelp/ 69 | DocProject/Help/*.HxT 70 | DocProject/Help/*.HxC 71 | DocProject/Help/*.hhc 72 | DocProject/Help/*.hhk 73 | DocProject/Help/*.hhp 74 | DocProject/Help/Html2 75 | DocProject/Help/html 76 | 77 | # Click-Once directory 78 | publish 79 | 80 | # Publish Web Output 81 | *.Publish.xml 82 | 83 | # NuGet Packages Directory 84 | packages 85 | 86 | # Windows Azure Build Output 87 | csx 88 | *.build.csdef 89 | 90 | # Windows Store app package directory 91 | AppPackages/ 92 | 93 | # Others 94 | [Bb]in 95 | [Oo]bj 96 | sql 97 | TestResults 98 | [Tt]est[Rr]esult* 99 | *.Cache 100 | ClientBin 101 | [Ss]tyle[Cc]op.* 102 | ~$* 103 | *.dbmdl 104 | Generated_Code #added for RIA/Silverlight projects 105 | 106 | # Backup & report files from converting an old project file to a newer 107 | # Visual Studio version. Backup files are not needed, because we have git ;-) 108 | _UpgradeReport_Files/ 109 | Backup*/ 110 | UpgradeLog*.XML 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Gnomic Studios 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FalconUDP 2 | 3 | FalconUDP is an application level protocol for sending and receiving small messages (one to one thousand or more bytes) frequently (once a minute to hundreds of times a second) to and from one or many connected peers. It has been implemented on top of the Internet User Datagram protocol (UDP). 4 | 5 | This is the official .NET implementation, implemented as a .NET Class Library: FalconUDP.dll. Throughout this repository "FalconUDP" is used interchangeably to refer to the protocol or this .NET implementation. 6 | 7 | FalconUDP was developed by Gnomic Studios for their multiplayer game Square Heroes: http://www.squareheroes.com/, check it out to see FalconUDP perform! 8 | 9 | ## Features 10 | * **Authentication and connection orientated** FalconUDP only communicates with previously authenticated peers. 11 | * **Reliable and in-order messages** Messages can be sent in-order, reliably, both in-order and reliable, or without any reliable and in-order checking and controls. Duplicates are always detected and dropped. 12 | * **Minimal overhead** FalconUDP adds minimal data to the user-application's data to be sent and received "on the wire" and includes support for "packing" additional user-application packets into existing outgoing messages. 13 | * **Latency estimation** FalconUDP measures round-trip-times from sending reliable messages till receiving the corresponding ACKnowlodgment, providing one way latency estimation to each connected peer. 14 | * **Any Topology** User-applications can use their own logical network topology such as Server-Client or Peer-to-Peer, since a "Falcon peer" can have one or more connected peers. 15 | * **Discovery** FalconUDP peers can "discover" other FalconUDP peers on the same subnet (if a FalconUDP peer is set to accept discovery requests and optionally the request has a valid token). 16 | * **NAT Traversal** Two FalconUDP peers behind NAT(s) can establish a connection with each other after connecting to third publically accessible server to negotiate the connection. 17 | * **Minimal garbage** FalconUDP makes extensive use of memory management techniques to avoid creating garbage such as: pooling objects, segmenting buffers from large ones (to prevent heap memory fragmentation) and copying data directly into and out of buffers. 18 | * **Statistics** Total bytes per second sent and received to/from all remote peers. 19 | * **Simulate** latency, jitter and packet loss options. 20 | 21 | ## Limitations 22 | * Currently only IPv4 23 | * No Congestion Control. FalconUDP provides no in-built congestion control. 24 | * No Encryption. FalconUDP has no in-built encryption. 25 | 26 | ## Protocol 27 | 28 | ``` 29 | [0] Packet detail byte (see below). 30 | 31 | [1] } 32 | [2] } Sequence number as unsigned 16 bit integer 33 | 34 | [3] } 35 | [4] } Payload Size as unsigned 16 bit integer 36 | 37 | [n] } 38 | ... } Payload (if any, i.e. size > 0) 39 | [m] } 40 | 41 | [ optional additional packet - if any bytes remain in datagram 42 | 43 | [m+1] Packet detail byte (see below). 44 | 45 | [m+2] } 46 | [m+3] } Payload Size as unsigned 16 bit integer 47 | 48 | [m+4] } 49 | ... } Payload (if any, i.e. size > 0) 50 | [o] } 51 | 52 | ... possible multiple additional packets 53 | ] 54 | ``` 55 | 56 | ### Packet Detail byte in bits 57 | 58 | ``` 59 | [0] 60 | [1] } 61 | [2] } Send Options 62 | [3] } 63 | 64 | [4] } 65 | [5] } 66 | [6] } Packet Type 67 | [7] } 68 | ``` 69 | 70 | ### ACKs 71 | 72 | ACKs are special, they are the same size as a FalonUDP header and have seq, Send Options and 73 | Packet Type stored at the same place, however the values have different meanings. A FalconUDP 74 | packet can start with an ACK or have them after any application packet within (this is so they 75 | can "piggy-back" on existing outgoing datagrams without triggering a new one). 76 | 77 | What each FalconUDP header value means in an ACK: 78 | 79 | ``` 80 | PacketType = Will always be ACK 81 | Sequence No. = Sequence ACK is for 82 | SendOptions = Channel ACK is for (however can be sent on any channel) 83 | Payload size = Undefined 84 | ``` 85 | 86 | ### NOTE: 87 | 88 | * All numbers in FalconUDP headers are unsigned integers stored in Little-Endian byte order. 89 | * Additional packets are only appended in a Falcon packet if they fit wholley within 90 | without packet exceeding MAX_PACKET_SIZE. 91 | * Additional packets have to have the same SendOptions as byte [1] in the Falcon header. 92 | 93 | ## TODO 94 | - include packet size and version no. in join request so incompatiable falcon peers cannot join eachother 95 | - peer who is not keepalive master does not get latency updates - should KeepAlive master tell them latency they have for peer? 96 | - consider determining addresses to broadcast to at time of discovery not at startup (could have changed, though it does take time to calc this) 97 | - in addition to above problem more generally local addresses cache should be refreshed when network changes have occured 98 | - quick disconnect when network disconnected 99 | - IPv6 and other underlying protocols support 100 | - Optimistic Reliable congestion controlled delivery 101 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | IF EXIST bin rmdir bin /S /Q 2 | IF EXIST obj rmdir obj /S /Q 3 | msbuild FalconUDP.sln /p:Configuration=Debug 4 | msbuild FalconUDP.sln /p:Configuration=MonoLinuxDebug 5 | msbuild FalconUDP.sln /p:Configuration=Release 6 | msbuild FalconUDP.sln /p:Configuration=MonoLinuxRelease -------------------------------------------------------------------------------- /src/AckDetail.cs: -------------------------------------------------------------------------------- 1 | namespace FalconUDP 2 | { 3 | internal struct AckDetail 4 | { 5 | internal ushort Seq; 6 | internal SendOptions Channel; 7 | internal float EllapsedSecondsSinceEnqueued; 8 | 9 | internal void Init(ushort seq, SendOptions channel) 10 | { 11 | this.Seq = seq; 12 | this.Channel = channel; 13 | this.EllapsedSecondsSinceEnqueued = 0.0f; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AutonomousTransciever.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Threading; 5 | using System.Collections.Generic; 6 | 7 | namespace FalconUDP 8 | { 9 | class AutonomousTransciever : IFalconTransceiver 10 | { 11 | struct DatagramDetail 12 | { 13 | public int Index, Count; 14 | public IPEndPoint IP; 15 | } 16 | 17 | private Socket socket; 18 | private IPEndPoint anyAddrEndPoint; 19 | private FalconPeer localPeer; 20 | private EndPoint placeHolderEndPoint = new IPEndPoint(IPAddress.Any, 30000); 21 | private Thread netThread; 22 | private byte[] lastDatagramBuffer; 23 | private byte[] receivedDatagramsBuffer; // 24 | private int receivedDatagramsIndex; // Updated from netThread, read from application; lock on receivedDatagramsBuffer 25 | private Queue receivedDatagramDetails; // 26 | 27 | public int BytesAvaliable 28 | { 29 | get 30 | { 31 | return receivedDatagramsIndex; 32 | } 33 | } 34 | 35 | public AutonomousTransciever(FalconPeer localPeer) 36 | { 37 | this.localPeer = localPeer; 38 | this.anyAddrEndPoint = new IPEndPoint(IPAddress.Any, localPeer.Port); 39 | this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 40 | this.lastDatagramBuffer = new byte[FalconPeer.MaxDatagramSize]; 41 | this.receivedDatagramsBuffer = new byte[localPeer.ReceiveBufferSize]; 42 | this.receivedDatagramDetails = new Queue(); 43 | } 44 | 45 | private void Run() 46 | { 47 | while (socket.IsBound) 48 | { 49 | int size = 0; 50 | try 51 | { 52 | size = socket.ReceiveFrom(lastDatagramBuffer, ref placeHolderEndPoint); 53 | } 54 | catch (SocketException ex) 55 | { 56 | localPeer.Log(LogLevel.Error, "SocketException ReceiveFrom " + ex.Message); 57 | continue; 58 | } 59 | 60 | if (size == 0) 61 | continue; 62 | if (size > FalconPeer.MaxDatagramSize) 63 | continue; 64 | 65 | lock (receivedDatagramsBuffer) 66 | { 67 | var detail = new DatagramDetail(); 68 | detail.Index = receivedDatagramsIndex; 69 | detail.Count = size; 70 | detail.IP = (IPEndPoint)placeHolderEndPoint; 71 | receivedDatagramDetails.Enqueue(detail); 72 | 73 | if (size > 0 && ((receivedDatagramsIndex + size) < receivedDatagramsBuffer.Length)) 74 | { 75 | Buffer.BlockCopy(lastDatagramBuffer, 0, receivedDatagramsBuffer, receivedDatagramsIndex, size); 76 | Interlocked.Exchange(ref receivedDatagramsIndex, (receivedDatagramsIndex + size)); 77 | } 78 | } 79 | } 80 | } 81 | 82 | public FalconOperationResult TryStart() 83 | { 84 | try 85 | { 86 | socket.Bind(anyAddrEndPoint); 87 | socket.ReceiveBufferSize = localPeer.ReceiveBufferSize; 88 | socket.SendBufferSize = localPeer.SendBufferSize; 89 | socket.EnableBroadcast = true; 90 | } 91 | catch (SocketException se) 92 | { 93 | // e.g. address already in use 94 | return new FalconOperationResult(se); 95 | } 96 | 97 | netThread = new Thread(Run); 98 | netThread.IsBackground = true; // terminate if the app does 99 | netThread.Start(); 100 | 101 | return FalconOperationResult.SuccessResult; 102 | } 103 | 104 | public void Stop() 105 | { 106 | try 107 | { 108 | socket.Close(); 109 | } 110 | catch { } 111 | try 112 | { 113 | netThread.Abort(); 114 | } 115 | catch { } 116 | } 117 | 118 | // returns 0 if fatal failure receiving from epFrom 119 | public int Read(byte[] receiveBuffer, ref IPEndPoint ipFrom) 120 | { 121 | lock (receivedDatagramsBuffer) 122 | { 123 | var detail = receivedDatagramDetails.Dequeue(); 124 | 125 | ipFrom = detail.IP; 126 | 127 | // If no more details remain all messages have been read from buffer, reset index 128 | // so future messages can re-use the receivedDatagramsBuffer. 129 | // 130 | // ASSUMPTION: We will never receive more messages than receivedDatagramsBuffer can hold 131 | // between reads to read all messages 132 | // 133 | if (receivedDatagramDetails.Count == 0) 134 | { 135 | receivedDatagramsIndex = 0; 136 | } 137 | 138 | if (detail.Count > 0) 139 | { 140 | Buffer.BlockCopy(receivedDatagramsBuffer, detail.Index, receiveBuffer, 0, detail.Count); 141 | } 142 | 143 | return detail.Count; 144 | } 145 | } 146 | 147 | // return false if fatal failure sending to ip 148 | public bool Send(byte[] buffer, int index, int count, IPEndPoint ip, bool expidite) 149 | { 150 | // NOTE: expidite ignored, setting EF results in exception on PS4 151 | 152 | try 153 | { 154 | socket.SendTo(buffer, index, count, SocketFlags.None, ip); 155 | } 156 | catch (SocketException se) 157 | { 158 | localPeer.Log(LogLevel.Error, String.Format("Socket Error {0}: {1}, sending to peer: {2}", se.ErrorCode.ToString(), se.Message, ip)); 159 | return false; 160 | } 161 | 162 | return true; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/AwaitingAcceptDetail.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace FalconUDP 4 | { 5 | class AwaitingAcceptDetail 6 | { 7 | internal IPEndPoint EndPoint; 8 | internal FalconOperationCallback Callback; 9 | internal float EllapsedSecondsSinceStart; 10 | internal int RetryCount; 11 | internal byte[] JoinData; 12 | internal Packet UserDataPacket; 13 | 14 | internal AwaitingAcceptDetail(IPEndPoint ip, FalconOperationCallback callback, byte[] joinData) 15 | { 16 | this.EndPoint = ip; 17 | this.Callback = callback; 18 | this.JoinData = joinData; 19 | this.EllapsedSecondsSinceStart = 0.0f; 20 | this.RetryCount = 0; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Const.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Text; 4 | 5 | namespace FalconUDP 6 | { 7 | static class Const 8 | { 9 | internal const int CARRIER_PROTOCOL_HEADER_SIZE = 28; // used for stats, total additional size in bytes sent on wire in additiona to falcon packet size 10 | internal const int FALCON_PACKET_HEADER_SIZE = 5; 11 | internal const int ADDITIONAL_PACKET_HEADER_SIZE = 3; // seq num is not included 12 | internal const int DISCOVERY_TOKEN_SIZE = 16; 13 | internal const byte SEND_OPTS_MASK = 112; // 0111 0000 AND'd with packet detail byte returns SendOptions 14 | internal const byte PACKET_TYPE_MASK = 15; // 0000 1111 AND'd with packet detail byte returns PacketType 15 | internal const byte ACK_PACKET_DETAIL = (byte)((byte)PacketType.ACK | (byte)SendOptions.None); 16 | internal static readonly Type PACKET_TYPE_TYPE = typeof(PacketType); 17 | internal static readonly Type SEND_OPTIONS_TYPE = typeof(SendOptions); 18 | internal static readonly byte[] CLASS_C_SUBNET_MASK = new byte[] { 255, 255, 255, 0 }; 19 | internal static readonly byte[] DISCOVER_PACKET = new byte[] { ((byte)PacketType.DiscoverRequest | (byte)SendOptions.None), 0, 0, 0, 0 }; 20 | internal static readonly byte[] DISCOVER_PACKET_WITH_TOKEN_HEADER = new byte[] { ((byte)PacketType.DiscoverRequest | (byte)SendOptions.None), 0, 0, DISCOVERY_TOKEN_SIZE, 0 }; 21 | internal static readonly byte[] UPNP_DISCOVER_REQUEST = Encoding.ASCII.GetBytes("M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nST:upnp:rootdevice\r\nMAN:\"ssdp:discover\"\r\nMX:3\r\n\r\n"); 22 | internal static readonly IPEndPoint UPNP_DISCOVER_ENDPOINT = new IPEndPoint(IPAddress.Broadcast, 1900); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/Datagram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FalconUDP 4 | { 5 | internal class Datagram 6 | { 7 | private readonly int originalSize; 8 | 9 | internal bool IsResend; 10 | internal ushort Sequence; 11 | internal SendOptions SendOptions; 12 | internal int ResentCount; 13 | internal float EllapsedSecondsSincePacketSent; 14 | internal TimeSpan EllapsedAtSent; 15 | 16 | public byte[] BackingBuffer { get; private set; } 17 | public int Offset { get; private set; } 18 | public int Count { get; private set; } 19 | public bool IsReliable 20 | { 21 | get { return (SendOptions & SendOptions.Reliable) == SendOptions.Reliable; } 22 | } 23 | public bool IsInOrder 24 | { 25 | get { return (SendOptions & SendOptions.InOrder) == SendOptions.InOrder; } 26 | } 27 | public int MaxSize 28 | { 29 | get { return originalSize; } 30 | } 31 | public PacketType Type 32 | { 33 | get { return (PacketType)(BackingBuffer[0] & Const.PACKET_TYPE_MASK); } 34 | } 35 | 36 | internal Datagram(byte[] backingBuffer, int offset, int count) 37 | { 38 | this.BackingBuffer = backingBuffer; 39 | this.Offset = offset; 40 | this.Count = count; 41 | this.originalSize = count; 42 | } 43 | 44 | internal void Resize(int newCount) 45 | { 46 | // can never be greater than original size 47 | if(Count > originalSize) 48 | throw new ArgumentOutOfRangeException("newCount", "cannot be greater than original count"); 49 | Count = newCount; 50 | } 51 | 52 | internal void Reset() 53 | { 54 | Count = originalSize; 55 | EllapsedSecondsSincePacketSent = 0.0f; 56 | ResentCount = 0; 57 | IsResend = false; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/DatagramPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace FalconUDP 6 | { 7 | internal class DatagramPool 8 | { 9 | private List bigBackingBuffers; 10 | private Stack pool; 11 | private readonly int buffersSize; 12 | private readonly int packetsPerBigBuffer; 13 | #if DEBUG 14 | private readonly List leased; 15 | #endif 16 | 17 | internal DatagramPool(int buffersSize, int initalNum) 18 | { 19 | this.buffersSize = buffersSize; 20 | this.pool = new Stack(initalNum); 21 | this.bigBackingBuffers = new List(); 22 | this.packetsPerBigBuffer = initalNum; 23 | #if DEBUG 24 | leased = new List(); 25 | #endif 26 | GrowPool(); 27 | } 28 | 29 | private void GrowPool() 30 | { 31 | byte[] bigBackingBuffer = new byte[buffersSize * packetsPerBigBuffer]; 32 | for (int i = 0; i < packetsPerBigBuffer; i++) 33 | { 34 | Datagram buffer = new Datagram(bigBackingBuffer, i * buffersSize, buffersSize); 35 | this.pool.Push(buffer); 36 | } 37 | bigBackingBuffers.Add(bigBackingBuffer); 38 | } 39 | 40 | internal Datagram Borrow() 41 | { 42 | if (pool.Count == 0) 43 | { 44 | Debug.WriteLine("***DatagramPool depleted, newing up another pool, each pool is {0}bytes.***", buffersSize * packetsPerBigBuffer); 45 | GrowPool(); 46 | } 47 | 48 | Datagram buffer = pool.Pop(); 49 | #if DEBUG 50 | leased.Add(buffer); 51 | #endif 52 | return buffer; 53 | } 54 | 55 | internal void Return(Datagram buffer) 56 | { 57 | #if DEBUG 58 | if (!leased.Contains(buffer)) 59 | throw new InvalidOperationException("item not leased"); 60 | leased.Remove(buffer); 61 | #endif 62 | // reset count to original size 63 | buffer.Reset(); 64 | 65 | pool.Push(buffer); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/DatagramSocketTransceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Runtime.InteropServices.WindowsRuntime; 5 | using System.Threading.Tasks; 6 | using Windows.Networking; 7 | using Windows.Networking.Sockets; 8 | using Windows.Storage.Streams; 9 | 10 | namespace FalconUDP 11 | { 12 | internal class DatagramSocketTransceiver : IFalconTransceiver 13 | { 14 | struct MessageDetail 15 | { 16 | public int Index, Count; 17 | public HostName RemoteAddress; 18 | public string RemotePort; 19 | } 20 | 21 | private DatagramSocket datagramSocket; 22 | private FalconPeer localPeer; 23 | private bool isEFSet; 24 | private int messagesBufferIndex; //} 25 | private readonly byte[] messagesBuffer; //} Updated from different threads, lock on messagesBuffer 26 | private readonly Queue messageDetails; //} 27 | private readonly Dictionary outputStreams; // cache of output streams keyed on their endpoint 28 | 29 | public int BytesAvaliable 30 | { 31 | get 32 | { 33 | lock (messagesBuffer) 34 | { 35 | return messagesBufferIndex; 36 | } 37 | } 38 | } 39 | 40 | internal DatagramSocketTransceiver(FalconPeer localPeer) 41 | { 42 | this.localPeer = localPeer; 43 | this.messagesBuffer = new byte[localPeer.ReceiveBufferSize]; 44 | this.messageDetails = new Queue(); 45 | this.outputStreams = new Dictionary(); 46 | 47 | } 48 | 49 | private void SetEF() 50 | { 51 | datagramSocket.Control.QualityOfService = SocketQualityOfService.LowLatency; 52 | isEFSet = true; 53 | } 54 | 55 | private void UnsetEF() 56 | { 57 | datagramSocket.Control.QualityOfService = SocketQualityOfService.Normal; 58 | isEFSet = false; 59 | } 60 | 61 | private void OnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args) 62 | { 63 | // NOTE: This event is raised on different threads 64 | 65 | lock (messagesBuffer) 66 | { 67 | try 68 | { 69 | using (var dr = args.GetDataReader()) // TODO how to prevent garbage here? https://social.msdn.microsoft.com/Forums/windowsapps/en-US/b1d490f7-a637-4648-925a-99fd7f55af1d/missing-datareader-and-datawriter-readbytes-and-writebytes-overloads-to-which-a?forum=winappswithcsharp#b1d490f7-a637-4648-925a-99fd7f55af1d 70 | { 71 | var size = (int)dr.UnconsumedBufferLength; 72 | var datagram = dr.ReadBuffer(dr.UnconsumedBufferLength); 73 | 74 | datagram.CopyTo(0, messagesBuffer, messagesBufferIndex, size); 75 | 76 | messageDetails.Enqueue(new MessageDetail 77 | { 78 | Index = messagesBufferIndex, 79 | Count = size, 80 | RemoteAddress = args.RemoteAddress, 81 | RemotePort = args.RemotePort 82 | }); 83 | 84 | messagesBufferIndex += size; 85 | } 86 | } 87 | catch (Exception) 88 | { 89 | // e.g. connection closed 90 | } 91 | } 92 | } 93 | 94 | public FalconOperationResult TryStart() 95 | { 96 | try 97 | { 98 | datagramSocket = new DatagramSocket(); 99 | 100 | SetEF(); 101 | 102 | datagramSocket.MessageReceived += OnMessageReceived; 103 | 104 | // HACK: We know this is not an async op and will be run in-line so avoid breaking our API 105 | // and "wait" for op to complete. 106 | // 107 | var asyncOp = datagramSocket.BindServiceNameAsync(localPeer.Port.ToString()); 108 | var task = asyncOp.AsTask(); 109 | task.Wait(); 110 | 111 | return FalconOperationResult.SuccessResult; 112 | } 113 | catch (AggregateException aex) 114 | { 115 | // e.g. address already in use 116 | return new FalconOperationResult(aex.InnerExceptions.Count > 0 ? aex.InnerExceptions[0] : aex); 117 | } 118 | } 119 | 120 | public void Stop() 121 | { 122 | datagramSocket.Dispose(); 123 | } 124 | 125 | public int Read(byte[] recieveBuffer, ref IPEndPoint ipFrom) 126 | { 127 | lock (messagesBuffer) 128 | { 129 | if (messageDetails.Count == 0) 130 | return 0; 131 | 132 | MessageDetail detail = messageDetails.Dequeue(); 133 | 134 | // If no more details remain all messages have been read from buffer, reset index 135 | // so future messages can re-use the messagesBuffer. 136 | // 137 | // ASSUMPTION: We will never receive more messages than messageBuffer can hold 138 | // between reads to read all messages 139 | // 140 | if (messageDetails.Count == 0) 141 | messagesBufferIndex = 0; 142 | 143 | // Try assign ipFrom to IPEndPoint message received from 144 | if (!IPEndPoint.TryParse(detail.RemoteAddress.RawName, detail.RemotePort, out ipFrom)) 145 | { 146 | localPeer.Log(LogLevel.Error, String.Format("Failed to parse remote end point: {0}:{1}", detail.RemoteAddress.RawName, detail.RemotePort)); 147 | return 0; 148 | } 149 | 150 | System.Buffer.BlockCopy(messagesBuffer, detail.Index, recieveBuffer, 0, detail.Count); 151 | 152 | return detail.Count; 153 | } 154 | } 155 | 156 | public bool Send(byte[] buffer, int index, int count, IPEndPoint ip, bool expidite) 157 | { 158 | // NOTE: NETFX_CORE does not support changing EF so we are stuck with what we set when 159 | // DatagramSocket created and ignore expidite. 160 | 161 | // The DatagramSocket sample (https://code.msdn.microsoft.com/windowsapps/DatagramSocket-sample-76a7d82b) 162 | // says creating OutputStreams per datagram is costly and recommends caching them. An 163 | // OutputStream is required per endpoint so keep one for each endpoint we send to. 164 | 165 | // TODO over an extended period and/or if communicating with many endpoints should clean-up old unused ones 166 | 167 | IOutputStream outputStream; 168 | if (!outputStreams.TryGetValue(ip, out outputStream)) 169 | { 170 | // HACK: We know this is not an async op and will be run in-line so avoid breaking our API 171 | // and "wait" for op to complete. 172 | // 173 | var asyncOp = datagramSocket.GetOutputStreamAsync(ip.Address, ip.PortAsString); 174 | var task = asyncOp.AsTask(); 175 | task.Wait(); 176 | 177 | outputStream = task.Result; 178 | } 179 | 180 | // How to avoid creating garbage with all these streams: https://social.msdn.microsoft.com/Forums/windowsapps/en-US/b1d490f7-a637-4648-925a-99fd7f55af1d/missing-datareader-and-datawriter-readbytes-and-writebytes-overloads-to-which-a?forum=winappswithcsharp#b1d490f7-a637-4648-925a-99fd7f55af1d 181 | 182 | try 183 | { 184 | var rtBuffer = buffer.AsBuffer(index, count); 185 | 186 | // HACK: We know this is not an async op and will be run in-line so avoid breaking our API 187 | // and "wait" for op to complete. 188 | // 189 | var asyncOp = outputStream.WriteAsync(rtBuffer); 190 | var task = asyncOp.AsTask(); 191 | task.Wait(); 192 | } 193 | catch (AggregateException aex) 194 | { 195 | var ex = aex.InnerExceptions.Count > 0 ? aex.InnerExceptions[0] : aex; 196 | localPeer.Log(LogLevel.Error, String.Format("DatagramSocket Error {0}: sending to peer: {1}", ex.Message, ip.ToString())); 197 | return false; 198 | } 199 | 200 | return true; 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/DelayedDatagram.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace FalconUDP 3 | { 4 | // used when simulating delay 5 | internal class DelayedDatagram 6 | { 7 | internal float EllapsedSecondsRemainingToDelay; 8 | internal Datagram Datagram; 9 | 10 | internal DelayedDatagram(float seconds, Datagram datagram) 11 | { 12 | EllapsedSecondsRemainingToDelay = seconds; 13 | Datagram = datagram; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Delegates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | 5 | namespace FalconUDP 6 | { 7 | /// 8 | /// Delegate for the event. 9 | /// 10 | /// 11 | /// Id of peer added 12 | /// 13 | /// Additional data sent by the peer sent in the join request. 14 | /// 15 | /// NOTE: Packet must NOT be held and used outside of event handler as it autmatically 16 | /// returned to the pool. 17 | public delegate void PeerAdded(int id, Packet userData); 18 | 19 | /// 20 | /// Delegate for the event. 21 | /// 22 | /// 23 | /// Id or peer dropped 24 | public delegate void PeerDropped(int id); 25 | 26 | /// 27 | /// Delegate for the event. 28 | /// 29 | /// IPEndPoint of the remote FalconUDP peer just discovered 30 | public delegate void PeerDiscovered(IPEndPoint ipEndPoint); 31 | 32 | /// 33 | /// Delegate for the event. 34 | /// 35 | /// Id of peer that send the Pong 36 | /// Ellapsed milliseconds since Ping sent. 37 | public delegate void PongReceivedFromPeer(int peerId, TimeSpan roundTripTime); 38 | 39 | /// 40 | /// Delegate for the event. 41 | /// 42 | /// IPEndPoint pong received from. 43 | /// Ellapsed seconds since Ping sent. 44 | public delegate void PongReceivedFromUnknownPeer(IPEndPoint ipEndPoint, TimeSpan roundTripTime); 45 | 46 | #if DEBUG 47 | /// 48 | /// Delegate to invoke when Falcon logs a line instead of logging to Debug. 49 | /// 50 | /// LogLevel . 51 | /// Line to be logged. 52 | public delegate void LogCallback(LogLevel lvl, string line); 53 | #endif 54 | 55 | /// 56 | /// Callback to call once discovery operation initated by 57 | /// has completed. 58 | /// 59 | /// Array of all IPEndPoints that replied to discovery request. 60 | public delegate void DiscoveryCallback(IPEndPoint[] discoveredPeers); 61 | 62 | /// 63 | /// Callback passed to 64 | /// 65 | /// called back once the punch through operation completes. 66 | /// 67 | /// True if received a reply from any one of the end points supplied, 68 | /// otherwise false. 69 | /// The first end point from which a reply was recived, only set if 70 | /// successfult i.e. is true. 71 | public delegate void PunchThroughCallback(bool success, IPEndPoint endPoint); 72 | 73 | /// 74 | /// Delegate to process a packet received from a remote peer. 75 | /// 76 | /// Packet to read data sent from peer. 77 | /// Packet will be in a read-only state and must not be held onto to be used later (it 78 | /// will be returned to the packet pool once the method this delegate points to completes. 79 | public delegate void ProcessReceivedPacket(Packet packet); 80 | 81 | /// 82 | /// Callbask supplied to 83 | /// called back once operation completes. 84 | /// 85 | /// TODO 86 | public delegate void AddUPnPPortMappingCallback(AddUPnPMappingResult result); 87 | } 88 | -------------------------------------------------------------------------------- /src/EmitDiscoverySignalTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Net; 5 | using FalconUDP; 6 | #if !NETFX_CORE 7 | using System.Net.Sockets; 8 | #endif 9 | 10 | namespace FalconUDP 11 | { 12 | internal class EmitDiscoverySignalTask 13 | { 14 | private readonly List endPointsToSendTo; 15 | private readonly List endPointsReceivedReplyFrom; 16 | private readonly byte[] signal; 17 | private bool listenForReply; // it is possible to emit discovery signals without bothering about a reply, e.g. to aid another peer joining us in an attempt to traverse NAT 18 | private DiscoveryCallback callback; 19 | private float secondsBetweenEmits; 20 | private int totalEmits; 21 | private int maxNumberPeersToDiscover; 22 | private FalconPeer falconPeer; 23 | private int emitCount; 24 | private Guid? token; 25 | private float ellapsedSecondsSinceLastEmit; 26 | 27 | public bool IsAwaitingDiscoveryReply { get { return listenForReply; } } 28 | public bool TaskEnded { get; private set; } 29 | 30 | public EmitDiscoverySignalTask() 31 | { 32 | endPointsToSendTo = new List(); 33 | endPointsReceivedReplyFrom = new List(); 34 | signal = new byte[Const.DISCOVER_PACKET_WITH_TOKEN_HEADER.Length + Const.DISCOVERY_TOKEN_SIZE]; 35 | } 36 | 37 | internal void EmitDiscoverySignal() 38 | { 39 | foreach (IPEndPoint ep in endPointsToSendTo) 40 | { 41 | // check we haven't already discovered the peer we are about to try discover! 42 | if (endPointsReceivedReplyFrom.Exists(dp => dp.FastEquals(ep))) 43 | continue; 44 | 45 | falconPeer.Log(LogLevel.Debug, String.Format("Emitting discovery signal to: {0}, with token: {1}.", ep, token.HasValue ? token.Value.ToString() : "None")); 46 | 47 | int count = token.HasValue ? signal.Length : Const.DISCOVER_PACKET.Length; 48 | 49 | //------------------------------------------------------- 50 | falconPeer.Transceiver.Send(signal, 0, count, ep, false); 51 | //------------------------------------------------------- 52 | } 53 | } 54 | 55 | internal void Update(float dt) 56 | { 57 | ellapsedSecondsSinceLastEmit += dt; 58 | 59 | if (ellapsedSecondsSinceLastEmit >= secondsBetweenEmits) 60 | { 61 | ++emitCount; 62 | ellapsedSecondsSinceLastEmit = 0.0f; 63 | 64 | if (emitCount == totalEmits) // an emit is sent when started 65 | { 66 | if (callback != null) 67 | { 68 | callback(endPointsReceivedReplyFrom.ToArray()); 69 | } 70 | this.TaskEnded = true; 71 | } 72 | else 73 | { 74 | EmitDiscoverySignal(); 75 | } 76 | } 77 | } 78 | 79 | internal void Init(FalconPeer falconPeer, 80 | bool listenForReply, 81 | float durationSeconds, 82 | int numOfSignalsToEmit, 83 | int maxNumOfPeersToDiscover, 84 | IEnumerable endPointsToSendTo, 85 | Guid? token, 86 | DiscoveryCallback callback) 87 | { 88 | // NOTE: This class is re-used from a pool so this method needs to fully reset 89 | // the class. 90 | 91 | if (listenForReply) 92 | Debug.Assert(callback != null, "callback required if listening for a reply"); 93 | else 94 | Debug.Assert(callback == null, "callback must be null if not listening for a reply"); 95 | Debug.Assert(maxNumOfPeersToDiscover > 0, "max no. of peers to receive a reply must be greater than 0"); 96 | 97 | this.TaskEnded = false; 98 | this.falconPeer = falconPeer; 99 | this.emitCount = 0; 100 | this.listenForReply = listenForReply; 101 | this.callback = callback; 102 | this.secondsBetweenEmits = (durationSeconds / numOfSignalsToEmit); 103 | this.totalEmits = numOfSignalsToEmit; 104 | this.maxNumberPeersToDiscover = maxNumOfPeersToDiscover; 105 | this.token = token; 106 | this.ellapsedSecondsSinceLastEmit = 0.0f; 107 | 108 | if (token.HasValue) 109 | { 110 | Buffer.BlockCopy(Const.DISCOVER_PACKET_WITH_TOKEN_HEADER, 0, signal, 0, Const.DISCOVER_PACKET_WITH_TOKEN_HEADER.Length); 111 | Buffer.BlockCopy(token.Value.ToByteArray(), 0, signal, Const.DISCOVER_PACKET.Length, Const.DISCOVERY_TOKEN_SIZE); 112 | } 113 | else 114 | { 115 | Buffer.BlockCopy(Const.DISCOVER_PACKET, 0, signal, 0, Const.DISCOVER_PACKET.Length); 116 | } 117 | 118 | this.endPointsToSendTo.Clear(); 119 | this.endPointsToSendTo.AddRange(endPointsToSendTo); 120 | this.endPointsReceivedReplyFrom.Clear(); 121 | } 122 | 123 | internal void AddDiscoveryReply(IPEndPoint endPointReceivedFrom) 124 | { 125 | // check we haven't already discovered this peer 126 | if (endPointsReceivedReplyFrom.Exists(ep => ep.FastEquals(endPointReceivedFrom))) 127 | return; 128 | 129 | // raise PeerDiscovered event 130 | falconPeer.RaisePeerDiscovered(endPointReceivedFrom); 131 | 132 | // add to list of end points received reply from 133 | endPointsReceivedReplyFrom.Add(endPointReceivedFrom); 134 | if (endPointsReceivedReplyFrom.Count == maxNumberPeersToDiscover) 135 | { 136 | callback(endPointsReceivedReplyFrom.ToArray()); 137 | callback = null; // prevent possible subsequent Tick() calling the callback again 138 | TaskEnded = true; 139 | } 140 | } 141 | 142 | internal bool IsForDiscoveryReply(IPEndPoint endPointDiscoveryReplyReceivedFrom) 143 | { 144 | // ASSUMPTION: There can only be one EmitDiscoverySignalTask at any one time that 145 | // matches (inc. broadcast addresses) any one discovery reply. 146 | 147 | #if !NETFX_CORE 148 | Debug.Assert(endPointDiscoveryReplyReceivedFrom.AddressFamily == AddressFamily.InterNetwork); 149 | #endif 150 | 151 | if (TaskEnded) 152 | return false; 153 | 154 | foreach (IPEndPoint endPointToSendTo in endPointsToSendTo) 155 | { 156 | if (endPointToSendTo.Port == endPointDiscoveryReplyReceivedFrom.Port) 157 | { 158 | if (endPointToSendTo.Address.Equals(endPointDiscoveryReplyReceivedFrom.Address)) 159 | { 160 | return true; 161 | } 162 | 163 | #if NETFX_CORE 164 | bool matches = endPointToSendTo.Address.Type == endPointDiscoveryReplyReceivedFrom.Address.Type; 165 | // TODO 166 | #else 167 | byte[] bytesFrom = endPointDiscoveryReplyReceivedFrom.Address.GetAddressBytes(); 168 | byte[] bytesTo = endPointToSendTo.Address.GetAddressBytes(); 169 | 170 | bool matches = ((bytesTo[0] == 255 || bytesFrom[0] == bytesTo[0]) 171 | && (bytesTo[1] == 255 || bytesFrom[1] == bytesTo[1]) 172 | && (bytesTo[2] == 255 || bytesFrom[2] == bytesTo[2]) 173 | && (bytesTo[3] == 255 || bytesFrom[3] == bytesTo[3])); 174 | #endif 175 | 176 | if (matches) 177 | return true; 178 | } 179 | } 180 | 181 | return false; 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Enums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FalconUDP 4 | { 5 | /// 6 | /// Severity of a log entry. 7 | /// 8 | public enum LogLevel 9 | { 10 | /// Debug 11 | Debug, 12 | /// Informational 13 | Info, 14 | /// Warning 15 | Warning, 16 | /// Error 17 | Error 18 | } 19 | 20 | /// 21 | /// Sequentiality and reliability control options for the delivery of a packet. 22 | /// 23 | /// 24 | /// Regardless of the SendOption value used packets are never proccessed more than once (in 25 | /// case duplicated), and will be dropped if they are our-of-order from the last packet the 26 | /// remote peer received from this peer with the same SendOptions by more than 27 | /// . 28 | /// 29 | [Flags] 30 | public enum SendOptions : byte 31 | { 32 | /// 33 | /// Guarantees packet arrival. 34 | /// 35 | /// If the remote peer does not ACKnowledge receipt of packet sent with this flag 36 | /// , re-send the packet till it does - a maximum of 37 | /// times. If after re-sends still no ACKnowldgement 38 | /// is recieved the remote peer is assumed to have disconnected (without saying bye) and is 39 | /// dropped. 40 | /// 41 | Reliable = 16, // 0001 0000 42 | /// 43 | /// Guarantees packet when received by remote peer is proccessed in-order in relation to 44 | /// packets it has already processed using this, and only this, SendOption. 45 | /// 46 | /// If remote peer has already processed a later packet this packet will be 47 | /// dropped. However even if received after a later packets, if the remote peer has not 48 | /// processed later packets yet: packets will be ordered correctly when application does read 49 | /// them. 50 | /// 51 | InOrder = 32, // 0010 0000 52 | /// 53 | /// Guarantee arrival () AND is processed in-order in relation to 54 | /// other packets sent using this, and only this, SendOption. 55 | /// 56 | /// Using this option guarantees all packets are received and proccessed in-order. 57 | /// However this involves more overhead: bandwidth because of the ACKs and memory as 58 | /// senders have to hold on to sent packets until they are ACKnowledged, and, receivers 59 | /// have to hold on to packets for which an earlier packet has not yet arrived. 60 | ReliableInOrder = 48, // 0011 0000 61 | /// No reliability or sequentiality guarantees. 62 | /// As with all packets: duplicates and way out-of-order packets will not be 63 | /// proccessed by a FalconUDP recipient. 64 | None = 64, // 0100 0000 65 | } 66 | 67 | // packet type (last 4 bits of packet info byte in header), max 15 values 68 | internal enum PacketType : byte 69 | { 70 | ACK, 71 | JoinRequest, 72 | AcceptJoin, 73 | Ping, 74 | Pong, 75 | Application, 76 | DiscoverRequest, 77 | DiscoverReply, 78 | Bye, 79 | KeepAlive 80 | } 81 | 82 | /// 83 | /// Result of attempting to add forwarding rule to first UPnP Internet Gateway device discovered on connected network 84 | /// 85 | public enum AddUPnPMappingResult 86 | { 87 | /// 88 | /// Rule successfull added 89 | /// 90 | Success, 91 | /// 92 | /// Rule was not added within time (it may still be added) 93 | /// 94 | FailedTimedOut, 95 | /// 96 | /// Failed to add rule becuase no active local address was found to forward to 97 | /// 98 | FailedNoActiveLocalAddress, 99 | /// 100 | /// Failed to add rule becuase compliant UPnP device not found or other reason 101 | /// 102 | FailedOther 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/ExceptionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FalconUDP 4 | { 5 | internal static class ExceptionHelper 6 | { 7 | internal static string GetFullDetails(this Exception ex) 8 | { 9 | string msg = ""; 10 | do 11 | { 12 | msg += String.Format("{0}{1}{0}{2}", Environment.NewLine, ex.Message, ex.StackTrace); 13 | ex = ex.InnerException; 14 | } while (ex != null); 15 | 16 | return msg; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/FalconExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace FalconUDP 4 | { 5 | internal static class FalconExtensions 6 | { 7 | internal static bool FastEquals(this IPEndPoint ip1, IPEndPoint ip2) 8 | { 9 | #if NETFX_CORE || WINDOWS_UWP 10 | return ip1.Equals(ip2); 11 | #else 12 | #pragma warning disable 0618 13 | return ip1.Address.Address.Equals(ip2.Address.Address) && ip1.Port == ip2.Port; 14 | #pragma warning restore 0618 15 | #endif 16 | // TODO if IPv6 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/FalconHelper.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | namespace FalconUDP 4 | { 5 | internal static class FalconHelper 6 | { 7 | internal static unsafe void WriteFalconHeader(byte[] dstBuffer, 8 | int dstIndex, 9 | PacketType type, 10 | SendOptions opts, 11 | ushort seq, 12 | ushort payloadSize) 13 | { 14 | fixed (byte* ptr = &dstBuffer[dstIndex]) 15 | { 16 | *ptr = (byte)((byte)opts | (byte)type); 17 | *(ushort*)(ptr + 1) = seq; 18 | *(ushort*)(ptr + 3) = payloadSize; 19 | } 20 | } 21 | 22 | internal static unsafe void WriteAdditionalFalconHeader(byte[] dstBuffer, 23 | int dstIndex, 24 | PacketType type, 25 | SendOptions opts, 26 | ushort payloadSize) 27 | { 28 | fixed (byte* ptr = &dstBuffer[dstIndex]) 29 | { 30 | *ptr = (byte)((byte)opts | (byte)type); 31 | *(ushort*)(ptr + 1) = payloadSize; 32 | } 33 | } 34 | 35 | internal static void WriteAck(AckDetail ack, byte[] dstBuffer, int dstIndex) 36 | { 37 | WriteFalconHeader(dstBuffer, dstIndex, PacketType.ACK, ack.Channel, ack.Seq, 0); 38 | } 39 | 40 | internal static uint GetNetMaskFromNumOfBits(int numOfBits) 41 | { 42 | return uint.MaxValue << (32 - numOfBits); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/FalconOperationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FalconUDP 4 | { 5 | /// 6 | /// Delegate used as callback once a Falcon operation completes. 7 | /// 8 | /// with the result of the operation. 9 | /// The type will be. 10 | public delegate void FalconOperationCallback(FalconOperationResult result); 11 | 12 | /// 13 | /// Result of a Falcon operation, successful or not. 14 | /// 15 | public class FalconOperationResult 16 | { 17 | /// 18 | /// Successful 19 | /// 20 | public static readonly FalconOperationResult SuccessResult = new FalconOperationResult(true); 21 | 22 | /// 23 | /// True if the operation was successul, otherwise false. 24 | /// 25 | public bool Success { get; private set; } 26 | 27 | /// 28 | /// Failure reason. Always set when is false. 29 | /// 30 | public string NonSuccessMessage { get; private set; } 31 | 32 | /// 33 | /// Set if an Exception was the cause of opertaion to fail. Only set when is false. 34 | /// 35 | public Exception Exception { get; private set; } 36 | 37 | internal FalconOperationResult(bool success, string nonSuccessMessage, Exception ex) 38 | { 39 | this.Success = success; 40 | this.NonSuccessMessage = nonSuccessMessage; 41 | this.Exception = ex; 42 | } 43 | 44 | internal FalconOperationResult(bool success, string nonSuccessMessage) 45 | : this(success, nonSuccessMessage, null) 46 | { 47 | } 48 | 49 | internal FalconOperationResult(bool success) 50 | : this(success, null, null) 51 | { 52 | } 53 | 54 | internal FalconOperationResult(Exception ex) 55 | : this(false, null, ex) 56 | { 57 | this.NonSuccessMessage = ex.GetFullDetails(); 58 | } 59 | } 60 | 61 | /// 62 | /// Result of a Falcon operation, successful or not containing specific result. 63 | /// 64 | public class FalconOperationResult 65 | { 66 | /// 67 | /// True if the operation was successul, otherwise false. 68 | /// 69 | public bool Success { get; private set; } 70 | 71 | /// 72 | /// Failure reason. Always set when is false. 73 | /// 74 | public string NonSuccessMessage { get; private set; } 75 | 76 | /// 77 | /// Set if an Exception was the cause of opertaion to fail. Only set when is false. 78 | /// 79 | public Exception Exception { get; private set; } 80 | 81 | /// 82 | /// Return value from the operation. 83 | /// 84 | public TReturnValue ReturnValue { get; private set; } 85 | 86 | internal FalconOperationResult(bool success, string nonSuccessMessage, Exception ex, TReturnValue returnValue) 87 | { 88 | this.Success = success; 89 | this.NonSuccessMessage = nonSuccessMessage; 90 | this.Exception = ex; 91 | this.ReturnValue = returnValue; 92 | } 93 | 94 | internal FalconOperationResult(bool success, string nonSuccessMessage, TReturnValue returnValue) 95 | : this(success, nonSuccessMessage, null, returnValue) 96 | { 97 | } 98 | 99 | internal FalconOperationResult(bool success, TReturnValue returnValue) 100 | : this(success, null, null, returnValue) 101 | { 102 | } 103 | 104 | internal FalconOperationResult(Exception ex, TReturnValue returnValue) 105 | : this(false, null, ex, returnValue) 106 | { 107 | this.NonSuccessMessage = ex.GetFullDetails(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/FalconUDP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Debug 4 | AnyCPU 5 | {03EE73A9-96B2-4A42-9177-2C70E5535257} 6 | Library 7 | false 8 | ClassLibrary 9 | v4.0 10 | 512 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug\ 17 | DEBUG;TRACE 18 | prompt 19 | 4 20 | true 21 | 22 | 23 | pdbonly 24 | true 25 | bin\Release\ 26 | TRACE 27 | prompt 28 | 4 29 | true 30 | 31 | 32 | FalconUDP 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 | -------------------------------------------------------------------------------- /src/FalconUDP.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FalconUDP", "FalconUDP.csproj", "{03EE73A9-96B2-4A42-9177-2C70E5535257}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {03EE73A9-96B2-4A42-9177-2C70E5535257}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {03EE73A9-96B2-4A42-9177-2C70E5535257}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {03EE73A9-96B2-4A42-9177-2C70E5535257}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {03EE73A9-96B2-4A42-9177-2C70E5535257}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {EDCC0FA2-B61C-4CA1-9BDF-3D1BAAC98DA8} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmnl/FalconUDP/92c8dad84a7e830d93c065613abc1afa863d5e94/src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmnl/FalconUDP/92c8dad84a7e830d93c065613abc1afa863d5e94/src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmnl/FalconUDP/92c8dad84a7e830d93c065613abc1afa863d5e94/src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmnl/FalconUDP/92c8dad84a7e830d93c065613abc1afa863d5e94/src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmnl/FalconUDP/92c8dad84a7e830d93c065613abc1afa863d5e94/src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmnl/FalconUDP/92c8dad84a7e830d93c065613abc1afa863d5e94/src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/StoreLogo.png -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmnl/FalconUDP/92c8dad84a7e830d93c065613abc1afa863d5e94/src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/FalconUDP.WindowsUniversal.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x86 7 | {CB902C7C-67D7-43DB-9480-DFC87F8169A5} 8 | AppContainerExe 9 | Properties 10 | FalconUDP.WindowsUniversal.Tests 11 | FalconUDP.WindowsUniversal.Tests 12 | en-US 13 | UAP 14 | 10.0.10586.0 15 | 10.0.10586.0 16 | 14 17 | 512 18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 19 | FalconUDP.WindowsUniversal.Tests_TemporaryKey.pfx 20 | 14.0 21 | 22 | 23 | true 24 | bin\x86\Debug\ 25 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 26 | ;2008 27 | full 28 | x86 29 | false 30 | prompt 31 | true 32 | 33 | 34 | bin\x86\Release\ 35 | TRACE;NETFX_CORE;WINDOWS_UWP 36 | true 37 | ;2008 38 | pdbonly 39 | x86 40 | false 41 | prompt 42 | true 43 | true 44 | 45 | 46 | true 47 | bin\ARM\Debug\ 48 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 49 | ;2008 50 | full 51 | ARM 52 | false 53 | prompt 54 | true 55 | 56 | 57 | bin\ARM\Release\ 58 | TRACE;NETFX_CORE;WINDOWS_UWP 59 | true 60 | ;2008 61 | pdbonly 62 | ARM 63 | false 64 | prompt 65 | true 66 | true 67 | 68 | 69 | true 70 | bin\x64\Debug\ 71 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 72 | ;2008 73 | full 74 | x64 75 | false 76 | prompt 77 | true 78 | 79 | 80 | bin\x64\Release\ 81 | TRACE;NETFX_CORE;WINDOWS_UWP 82 | true 83 | ;2008 84 | pdbonly 85 | x64 86 | false 87 | prompt 88 | true 89 | true 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | FalconPeerTests.cs 100 | 101 | 102 | PacketTests.cs 103 | 104 | 105 | 106 | UnitTestApp.xaml 107 | 108 | 109 | 110 | 111 | MSBuild:Compile 112 | Designer 113 | 114 | 115 | 116 | 117 | Designer 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | {D5CCD9EE-B2F1-47BB-8D72-00FC35453428} 134 | FalconUDP.WindowsUniversal 135 | 136 | 137 | 138 | 139 | 140 | 141 | 14.0 142 | 143 | 144 | 151 | -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/FalconUDP.WindowsUniversal.Tests_TemporaryKey.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmnl/FalconUDP/92c8dad84a7e830d93c065613abc1afa863d5e94/src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/FalconUDP.WindowsUniversal.Tests_TemporaryKey.pfx -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | FalconUDP.WindowsUniversal.Tests 16 | markm 17 | Assets\StoreLogo.png 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FalconUDP.WindowsUniversal.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("FalconUDP.WindowsUniversal.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | [assembly: AssemblyMetadata("TargetPlatform","UAP")] 17 | 18 | // Version information for an assembly consists of the following four values: 19 | // 20 | // Major Version 21 | // Minor Version 22 | // Build Number 23 | // Revision 24 | // 25 | // You can specify all the values or you can default the Build and Revision Numbers 26 | // by using the '*' as shown below: 27 | // [assembly: AssemblyVersion("1.0.*")] 28 | [assembly: AssemblyVersion("1.0.0.0")] 29 | [assembly: AssemblyFileVersion("1.0.0.0")] 30 | [assembly: ComVisible(false)] -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/Properties/UnitTestApp.rd.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/UnitTestApp.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/UnitTestApp.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices.WindowsRuntime; 6 | using Windows.ApplicationModel; 7 | using Windows.ApplicationModel.Activation; 8 | using Windows.Foundation; 9 | using Windows.Foundation.Collections; 10 | using Windows.UI.Xaml; 11 | using Windows.UI.Xaml.Controls; 12 | using Windows.UI.Xaml.Controls.Primitives; 13 | using Windows.UI.Xaml.Data; 14 | using Windows.UI.Xaml.Input; 15 | using Windows.UI.Xaml.Media; 16 | using Windows.UI.Xaml.Navigation; 17 | 18 | namespace FalconUDP.WindowsUniversal.Tests 19 | { 20 | /// 21 | /// Provides application-specific behavior to supplement the default Application class. 22 | /// 23 | sealed partial class App : Application 24 | { 25 | /// 26 | /// Initializes the singleton application object. This is the first line of authored code 27 | /// executed, and as such is the logical equivalent of main() or WinMain(). 28 | /// 29 | public App() 30 | { 31 | this.InitializeComponent(); 32 | this.Suspending += OnSuspending; 33 | } 34 | 35 | /// 36 | /// Invoked when the application is launched normally by the end user. Other entry points 37 | /// will be used such as when the application is launched to open a specific file. 38 | /// 39 | /// Details about the launch request and process. 40 | protected override void OnLaunched(LaunchActivatedEventArgs e) 41 | { 42 | 43 | #if DEBUG 44 | if (System.Diagnostics.Debugger.IsAttached) 45 | { 46 | this.DebugSettings.EnableFrameRateCounter = true; 47 | } 48 | #endif 49 | 50 | Frame rootFrame = Window.Current.Content as Frame; 51 | 52 | // Do not repeat app initialization when the Window already has content, 53 | // just ensure that the window is active 54 | if (rootFrame == null) 55 | { 56 | // Create a Frame to act as the navigation context and navigate to the first page 57 | rootFrame = new Frame(); 58 | 59 | rootFrame.NavigationFailed += OnNavigationFailed; 60 | 61 | if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) 62 | { 63 | //TODO: Load state from previously suspended application 64 | } 65 | 66 | // Place the frame in the current Window 67 | Window.Current.Content = rootFrame; 68 | } 69 | 70 | Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI(); 71 | 72 | // Ensure the current window is active 73 | Window.Current.Activate(); 74 | 75 | Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments); 76 | } 77 | 78 | /// 79 | /// Invoked when Navigation to a certain page fails 80 | /// 81 | /// The Frame which failed navigation 82 | /// Details about the navigation failure 83 | void OnNavigationFailed(object sender, NavigationFailedEventArgs e) 84 | { 85 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName); 86 | } 87 | 88 | /// 89 | /// Invoked when application execution is being suspended. Application state is saved 90 | /// without knowing whether the application will be terminated or resumed with the contents 91 | /// of memory still intact. 92 | /// 93 | /// The source of the suspend request. 94 | /// Details about the suspend request. 95 | private void OnSuspending(object sender, SuspendingEventArgs e) 96 | { 97 | var deferral = e.SuspendingOperation.GetDeferral(); 98 | //TODO: Save application state and stop any background activity 99 | deferral.Complete(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDP.WindowsUniversal.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0" 4 | }, 5 | "frameworks": { 6 | "uap10.0": {} 7 | }, 8 | "runtimes": { 9 | "win10-arm": {}, 10 | "win10-arm-aot": {}, 11 | "win10-x86": {}, 12 | "win10-x86-aot": {}, 13 | "win10-x64": {}, 14 | "win10-x64-aot": {} 15 | } 16 | } -------------------------------------------------------------------------------- /src/FalconUDPTests/FalconUDPTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {F5096E04-4695-4982-99FD-6EC896E8ED5E} 10 | Library 11 | Properties 12 | FalconUDPTests 13 | FalconUDPTests 14 | v4.7.2 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | false 37 | 38 | 39 | true 40 | bin\x86\Debug\ 41 | DEBUG;TRACE 42 | full 43 | x86 44 | prompt 45 | false 46 | 47 | 48 | bin\x86\Release\ 49 | TRACE 50 | true 51 | pdbonly 52 | x86 53 | prompt 54 | false 55 | false 56 | false 57 | 58 | 59 | true 60 | bin\MonoLinuxDebug\ 61 | DEBUG;TRACE 62 | full 63 | AnyCPU 64 | prompt 65 | true 66 | true 67 | false 68 | false 69 | 70 | 71 | true 72 | bin\x86\MonoLinuxDebug\ 73 | DEBUG;TRACE 74 | full 75 | x86 76 | prompt 77 | true 78 | true 79 | false 80 | false 81 | 82 | 83 | bin\MonoLinuxRelease\ 84 | TRACE 85 | true 86 | pdbonly 87 | AnyCPU 88 | prompt 89 | false 90 | 91 | 92 | bin\x86\MonoLinuxRelease\ 93 | TRACE 94 | true 95 | pdbonly 96 | x86 97 | prompt 98 | false 99 | false 100 | false 101 | false 102 | 103 | 104 | true 105 | bin\MonoPS4Debug\ 106 | DEBUG;TRACE 107 | full 108 | AnyCPU 109 | prompt 110 | true 111 | true 112 | false 113 | 114 | 115 | true 116 | bin\x86\MonoPS4Debug\ 117 | DEBUG;TRACE 118 | full 119 | x86 120 | prompt 121 | true 122 | true 123 | false 124 | 125 | 126 | bin\MonoPS4Release\ 127 | TRACE 128 | true 129 | pdbonly 130 | AnyCPU 131 | prompt 132 | false 133 | false 134 | false 135 | 136 | 137 | bin\x86\MonoPS4Release\ 138 | TRACE 139 | true 140 | pdbonly 141 | x86 142 | prompt 143 | false 144 | false 145 | false 146 | 147 | 148 | 149 | 150 | 151 | 3.5 152 | 153 | 154 | 155 | 156 | False 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | {5CDA9619-2E01-423B-B890-5D84E5A41CE9} 167 | FalconUDP 168 | 169 | 170 | 171 | 178 | -------------------------------------------------------------------------------- /src/FalconUDPTests/PacketTests.cs: -------------------------------------------------------------------------------- 1 | #if WINDOWS_UWP 2 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 3 | #else 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | #endif 6 | using FalconUDP; 7 | using System; 8 | using System.Linq; 9 | 10 | namespace FalconUDPTests 11 | { 12 | [TestClass] 13 | public class PacketTests 14 | { 15 | [TestMethod] 16 | public void WriteReadBytesTest() 17 | { 18 | const int PACKET_SIZE = 10000; 19 | 20 | var pool = new PacketPool(PACKET_SIZE, 1); 21 | var packet = pool.Borrow(); 22 | var bytes = new byte[PACKET_SIZE]; 23 | var rand = new Random(); 24 | 25 | rand.NextBytes(bytes); 26 | 27 | packet.WriteBytes(bytes); 28 | 29 | Assert.AreEqual(PACKET_SIZE, packet.BytesWritten); 30 | 31 | packet.ResetAndMakeReadOnly(0); 32 | 33 | var readBytes = packet.ReadBytes(PACKET_SIZE); 34 | 35 | Assert.AreEqual(0, packet.BytesRemaining); 36 | Assert.IsTrue(Enumerable.SequenceEqual(bytes, readBytes), "Bytes written not the same when read!"); 37 | } 38 | 39 | [TestMethod] 40 | #if !NETFX_CORE 41 | [ExpectedException(typeof(ArgumentException))] 42 | #endif 43 | public void WriteOverflowTest() 44 | { 45 | const int PACKET_SIZE = 12; 46 | 47 | var pool = new PacketPool(PACKET_SIZE, 1); 48 | var packet = pool.Borrow(); 49 | var bytes = new byte[PACKET_SIZE + 1]; 50 | 51 | #if NETFX_CORE 52 | Assert.ThrowsException(() => packet.WriteBytes(bytes)); 53 | #else 54 | packet.WriteBytes(bytes); 55 | #endif 56 | } 57 | 58 | [TestMethod] 59 | #if !NETFX_CORE 60 | [ExpectedException(typeof(ArgumentException))] 61 | #endif 62 | public void ReadOverflowTest() 63 | { 64 | const int PACKET_SIZE = 12; 65 | 66 | var pool = new PacketPool(PACKET_SIZE, 1); 67 | var packet = pool.Borrow(); 68 | var bytes = new byte[4]; 69 | 70 | packet.WriteBytes(bytes); 71 | packet.ResetAndMakeReadOnly(0); 72 | 73 | #if NETFX_CORE 74 | Assert.ThrowsException(() => packet.ReadBytes(bytes.Length+1)); 75 | #else 76 | packet.ReadBytes(bytes.Length+1); 77 | #endif 78 | 79 | } 80 | 81 | [TestMethod] 82 | public void RandomStressTest() 83 | { 84 | const int PACKET_SIZE = 123; 85 | const int NUM_OF_LOOPS = 10000; 86 | 87 | var pool = new PacketPool(PACKET_SIZE, 1); 88 | var packet = pool.Borrow(); 89 | var rand = new Random(); 90 | 91 | for (var i = 0; i < NUM_OF_LOOPS; i++) 92 | { 93 | packet.Init(); 94 | var bytes = new byte[rand.Next(PACKET_SIZE+1)]; 95 | rand.NextBytes(bytes); 96 | packet.WriteBytes(bytes); 97 | Assert.AreEqual(bytes.Length, packet.BytesWritten); 98 | packet.ResetAndMakeReadOnly(0); 99 | var numOfBytesToRead = rand.Next(bytes.Length + 1); 100 | var readBytes = packet.ReadBytes(numOfBytesToRead); 101 | Assert.AreEqual(bytes.Length - numOfBytesToRead, packet.BytesRemaining); 102 | Assert.IsTrue(Enumerable.SequenceEqual(bytes.Take(numOfBytesToRead), readBytes.Take(numOfBytesToRead)), "Bytes written not the same when read!"); 103 | } 104 | } 105 | 106 | [TestMethod] 107 | public void TestReadWriteVariableLengthInt32() 108 | { 109 | int[] valuesToTest = { 0, 1, 127, 128, 16383, 16384, 2097151, 2097152, 268435455, 268435456, Int32.MaxValue }; 110 | var pool = new PacketPool(5, 1); 111 | var packet = pool.Borrow(); 112 | 113 | int valueAsRead; 114 | foreach(int i in valuesToTest) 115 | { 116 | packet.WriteVariableLengthInt32(i); 117 | packet.ResetAndMakeReadOnly(-1); 118 | valueAsRead = packet.ReadVariableLengthInt32(); 119 | Assert.AreEqual(i, valueAsRead, "Value written: {0}, not as read: {1}", i, valueAsRead); 120 | packet.Init(); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/FalconUDPTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FalconUDPTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("FalconUDPTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2013 - 2014 Gnomic Studios")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e183aa74-f84d-4fac-aa91-ba9730f29333")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/GenericObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace FalconUDP 5 | { 6 | internal class GenericObjectPool where T: new() 7 | { 8 | private Stack pool; 9 | #if DEBUG 10 | private List leased; 11 | #endif 12 | 13 | internal GenericObjectPool(int initalCapacity) 14 | { 15 | this.pool = new Stack(); 16 | 17 | for (int i = 0; i < initalCapacity; i++) 18 | { 19 | this.pool.Push(new T()); 20 | } 21 | #if DEBUG 22 | leased = new List(); 23 | #endif 24 | } 25 | 26 | internal T Borrow() 27 | { 28 | T item = pool.Count > 0 ? pool.Pop() : new T(); 29 | 30 | #if DEBUG 31 | if(leased.Contains(item)) 32 | throw new InvalidOperationException("item already leased"); 33 | leased.Add(item); 34 | #endif 35 | return item; 36 | } 37 | 38 | internal void Return(T item) 39 | { 40 | #if DEBUG 41 | if (!leased.Contains(item)) 42 | throw new InvalidOperationException("item not leased"); 43 | leased.Remove(item); 44 | #endif 45 | pool.Push(item); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/IFalconTransceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace FalconUDP 5 | { 6 | internal interface IFalconTransceiver 7 | { 8 | int BytesAvaliable { get; } 9 | FalconOperationResult TryStart(); 10 | void Stop(); 11 | int Read(byte[] recieveBuffer, ref IPEndPoint ipFrom); 12 | bool Send(byte[] buffer, int index, int count, IPEndPoint ip, bool expidite); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Packet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Text; 4 | #if WINDOWS_UWP 5 | 6 | #endif 7 | 8 | namespace FalconUDP 9 | { 10 | /// 11 | /// Represents a Packet to be sent or received. 12 | /// 13 | /// 14 | /// An instance of a Packet must be obtained from . 15 | /// Once finished with the packet must be returned to the pool using 16 | /// , the Packet must not be re-used, always get another Packet by borrowing another one from 17 | /// . 18 | /// 19 | public class Packet 20 | { 21 | /// 22 | /// FalconUDP Peer Id this packet was received from. Only set on received packets. 23 | /// 24 | public int PeerId { get; internal set; } 25 | 26 | /// 27 | /// Number bytes remaining to be read from this Packet from the current pos. 28 | /// 29 | public int BytesRemaining { get { return BytesWritten - (pos - offset); } } 30 | 31 | /// 32 | /// Number of bytes written to this Packet. 33 | /// 34 | public int BytesWritten { get; private set; } 35 | 36 | internal bool IsReadOnly; 37 | internal ushort DatagramSeq; 38 | 39 | private byte[] backingBuffer; 40 | private int offset; 41 | private int count; 42 | private int pos; 43 | 44 | // do not construct, get from a pool 45 | internal Packet(byte[] backingBuffer, int offset, int count) 46 | { 47 | this.backingBuffer = backingBuffer; 48 | this.offset = offset; 49 | this.count = count; 50 | } 51 | 52 | private void PreRead(int size) 53 | { 54 | if(!IsReadOnly) 55 | throw new InvalidOperationException("Packet is write-only, borrow another from the pool."); 56 | if(size > BytesRemaining) 57 | throw new ArgumentException("Not enough bytes remain to read from current position"); 58 | } 59 | 60 | private void PreWrite(int size) 61 | { 62 | if(IsReadOnly) 63 | throw new InvalidOperationException("Packet is read-only, borrow another from the pool."); 64 | if(size > (count - (pos - offset))) 65 | throw new ArgumentException("Not enough bytes avaliable to write from current position"); 66 | BytesWritten += size; 67 | } 68 | 69 | /// 70 | /// Resets the pos, marks as read only and sets the peer id from. 71 | /// 72 | /// Falcon Peer Id packet received from. 73 | internal void ResetAndMakeReadOnly(int peerId) 74 | { 75 | pos = offset; 76 | IsReadOnly = true; 77 | PeerId = peerId; 78 | } 79 | 80 | internal void Init() 81 | { 82 | // NOTE: This should fully reset this Packet as is called when re-used from the pool. 83 | 84 | pos = offset; 85 | BytesWritten = 0; 86 | IsReadOnly = false; 87 | DatagramSeq = 0; 88 | } 89 | 90 | internal void CopyBytes(int index, byte[] destination, int dstOffset, int size) 91 | { 92 | if (index > BytesWritten) 93 | throw new ArgumentOutOfRangeException("index"); 94 | if (index + size > BytesWritten) 95 | throw new InvalidOperationException("Not enough bytes remain to copy count from index"); 96 | if (size == 0) 97 | return; 98 | 99 | Buffer.BlockCopy(backingBuffer, offset + index, destination, dstOffset, size); 100 | } 101 | 102 | internal byte[] ToBytes() 103 | { 104 | byte[] bytes = new byte[BytesWritten]; 105 | if(BytesWritten > 0) 106 | Buffer.BlockCopy(backingBuffer, offset, bytes, 0, BytesWritten); 107 | return bytes; 108 | } 109 | 110 | /// 111 | /// Copies all public members and underlying bytes written from 112 | /// to . 113 | /// 114 | /// Packet to copy from 115 | /// Packet to copy to 116 | /// If true resets 's current pos to 117 | /// the beginning, and makes it read-only. 118 | public static void Clone(Packet srcPacket, Packet dstPacket, bool reset) 119 | { 120 | if(dstPacket.count != srcPacket.count) 121 | throw new InvalidOperationException("packets are differnt sizes"); 122 | 123 | Buffer.BlockCopy(srcPacket.backingBuffer, srcPacket.offset, dstPacket.backingBuffer, dstPacket.offset, srcPacket.BytesWritten); 124 | dstPacket.BytesWritten = srcPacket.BytesWritten; 125 | dstPacket.DatagramSeq = srcPacket.DatagramSeq; 126 | if (reset) 127 | { 128 | dstPacket.ResetAndMakeReadOnly(srcPacket.PeerId); 129 | } 130 | else 131 | { 132 | dstPacket.pos = dstPacket.offset + (srcPacket.pos - srcPacket.offset); 133 | dstPacket.IsReadOnly = srcPacket.IsReadOnly; 134 | dstPacket.PeerId = srcPacket.PeerId; 135 | } 136 | } 137 | 138 | 139 | /// 140 | /// Reads next in this Packet. 141 | /// 142 | /// Next in this Packet. 143 | public byte ReadByte() 144 | { 145 | PreRead(sizeof(byte)); 146 | byte rv = backingBuffer[pos]; 147 | pos += sizeof(byte); 148 | return rv; 149 | } 150 | 151 | /// 152 | /// Reads next from the Packet. 153 | /// 154 | /// Next from this Packet. 155 | /// Takes up one byte 156 | public bool ReadBool() 157 | { 158 | PreRead(sizeof(bool)); 159 | bool rv = backingBuffer[pos] > 0; 160 | pos += sizeof(bool); 161 | return rv; 162 | } 163 | 164 | /// 165 | /// Reads next from this packet. 166 | /// 167 | /// Next from this Packet. 168 | public short ReadInt16() 169 | { 170 | PreRead(sizeof(short)); 171 | short rv = BitConverter.ToInt16(backingBuffer, pos); 172 | pos += sizeof(short); 173 | return rv; 174 | } 175 | 176 | /// 177 | /// Reads next from this packet. 178 | /// 179 | /// Next from this Packet. 180 | public ushort ReadUInt16() 181 | { 182 | PreRead(sizeof(ushort)); 183 | ushort rv = BitConverter.ToUInt16(backingBuffer, pos); 184 | pos += sizeof(ushort); 185 | return rv; 186 | } 187 | 188 | /// 189 | /// Reads next from this Packet. 190 | /// 191 | /// Next from this Packet. 192 | public int ReadInt32() 193 | { 194 | PreRead(sizeof(int)); 195 | int rv = BitConverter.ToInt32(backingBuffer, pos); 196 | pos += sizeof(int); 197 | return rv; 198 | } 199 | 200 | /// 201 | /// Reads next from this Packet. 202 | /// 203 | /// next from this Packet. 204 | public uint ReadUInt32() 205 | { 206 | PreRead(sizeof(uint)); 207 | uint rv = BitConverter.ToUInt32(backingBuffer, pos); 208 | pos += sizeof(uint); 209 | return rv; 210 | } 211 | 212 | /// 213 | /// Reads next from this Packet. 214 | /// 215 | /// next from this Packet. 216 | public long ReadInt64() 217 | { 218 | PreRead(sizeof(long)); 219 | long rv = BitConverter.ToInt64(backingBuffer, pos); 220 | pos += sizeof(long); 221 | return rv; 222 | } 223 | 224 | /// 225 | /// Reads next from this Packet. 226 | /// 227 | /// next from this Packet. 228 | public ulong ReadUInt64() 229 | { 230 | PreRead(sizeof(ulong)); 231 | ulong rv = BitConverter.ToUInt64(backingBuffer, pos); 232 | pos += sizeof(ulong); 233 | return rv; 234 | } 235 | 236 | /// 237 | /// Reads next from this Packet. 238 | /// 239 | /// next from this Packet. 240 | public float ReadSingle() 241 | { 242 | PreRead(sizeof(float)); 243 | float rv = BitConverter.ToSingle(backingBuffer, pos); 244 | pos += sizeof(float); 245 | return rv; 246 | } 247 | 248 | /// 249 | /// Reads next from this Packet. 250 | /// 251 | /// next from this Packet. 252 | public double ReadDouble() 253 | { 254 | PreRead(sizeof(double)); 255 | double rv = BitConverter.ToDouble(backingBuffer, pos); 256 | pos += sizeof(double); 257 | return rv; 258 | } 259 | 260 | /// 261 | /// Reads next bytes into a new byte[] from this Packet. 262 | /// 263 | /// Number of bytes to read 264 | /// Byte array with bytes from this Packet. 265 | public byte[] ReadBytes(int count) 266 | { 267 | PreRead(count); 268 | byte[] bytes = new byte[count]; 269 | Buffer.BlockCopy(backingBuffer, pos, bytes, 0, count); 270 | pos += count; 271 | return bytes; 272 | } 273 | 274 | /// 275 | /// Reads next bytes intp from . 276 | /// 277 | /// Destination array to copy bytes into. 278 | /// Index at which to start copying. 279 | /// Number of bytes to copy. 280 | public void ReadBytes(byte[] destination, int dstOffset, int count) 281 | { 282 | PreRead(count); 283 | Buffer.BlockCopy(backingBuffer, pos, destination, dstOffset, count); 284 | pos += count; 285 | } 286 | 287 | /// 288 | /// Reads text encoded in 289 | /// bytes long from this Packet. 290 | /// 291 | /// text is in. 292 | /// Number of bytes text is for. 293 | /// Text as 294 | public string ReadString(Encoding encoding, int lengthInBytes) 295 | { 296 | string rv = encoding.GetString(backingBuffer, pos, lengthInBytes); 297 | pos += lengthInBytes; 298 | return rv; 299 | } 300 | 301 | /// 302 | /// Reads size then text encoded in 303 | /// for the number bytes in size. 304 | /// 305 | /// text is in. 306 | /// Text as 307 | public string ReadStringPrefixedWithSize(Encoding encoding) 308 | { 309 | int length = ReadUInt16(); 310 | if(length == 0) 311 | return null; 312 | return ReadString(encoding, length); 313 | } 314 | 315 | /// 316 | /// Reads from this Packet. 317 | /// 318 | /// An 319 | public IPEndPoint ReadIPEndPoint() 320 | { 321 | long addr = ReadInt64(); 322 | int port = ReadUInt16(); 323 | 324 | #if NETFX_CORE 325 | return new IPEndPoint((uint)addr, (ushort)port); 326 | #else 327 | return new IPEndPoint(addr, port); 328 | #endif 329 | } 330 | 331 | /// 332 | /// Reads a 16 byte from this Packet. 333 | /// 334 | /// 335 | public Guid ReadGuid() 336 | { 337 | return new Guid(ReadBytes(16)); 338 | } 339 | 340 | /// 341 | /// Reads a variable lengh Int32 from Packet which depending on value takes up 1 to 5 bytes. 342 | /// TODO negative values not supported 343 | /// 344 | /// 345 | /// 346 | /// 347 | /// The number of bytes the value will take: 348 | /// 0 - 127 : 1 byte 349 | /// 128 - 16383 : 2 bytes 350 | /// 16384 - 2097151 : 3 bytes 351 | /// 2097152 - 268435455 : 4 bytes 352 | /// 268435456 - 2147483647 : 5 bytes 353 | /// 354 | /// 355 | public int ReadVariableLengthInt32() 356 | { 357 | byte currentByte; 358 | int value = 0; 359 | int shift = 0; 360 | do 361 | { 362 | currentByte = ReadByte(); 363 | value |= (currentByte & 0x7F) << shift; 364 | shift += 7; 365 | } while ((currentByte & 128) != 0); 366 | 367 | return value; 368 | } 369 | 370 | /// 371 | /// Reads next from current position without advancing current position. 372 | /// 373 | /// from current position. 374 | public byte PeekByte() 375 | { 376 | PreRead(sizeof(byte)); 377 | return backingBuffer[pos]; 378 | } 379 | 380 | /// 381 | /// Reads next from current position without advancing current position. 382 | /// 383 | /// from current position. 384 | public ushort PeekUInt16() 385 | { 386 | PreRead(sizeof(ushort)); 387 | return BitConverter.ToUInt16(backingBuffer, pos); 388 | } 389 | 390 | /// 391 | /// Reads next from current position without advancing current position. 392 | /// 393 | /// from current position. 394 | public short PeekInt16() 395 | { 396 | PreRead(sizeof(short)); 397 | return BitConverter.ToInt16(backingBuffer, pos); 398 | } 399 | 400 | /// 401 | /// Reads next from current position without advancing current position. 402 | /// 403 | /// from current position. 404 | public uint PeekUInt32() 405 | { 406 | PreRead(sizeof(uint)); 407 | return BitConverter.ToUInt32(backingBuffer, pos); 408 | } 409 | 410 | /// 411 | /// Reads next from current position without advancing current position. 412 | /// 413 | /// from current position. 414 | public int PeekInt32() 415 | { 416 | PreRead(sizeof(int)); 417 | return BitConverter.ToInt32(backingBuffer, pos); 418 | } 419 | 420 | /// 421 | /// Reads next from current position without advancing current position. 422 | /// 423 | /// from current position. 424 | public ulong PeekUInt64() 425 | { 426 | PreRead(sizeof(ulong)); 427 | return BitConverter.ToUInt64(backingBuffer, pos); 428 | } 429 | 430 | /// 431 | /// Reads next from current position without advancing current position. 432 | /// 433 | /// from current position. 434 | public long PeekInt64() 435 | { 436 | PreRead(sizeof(long)); 437 | return BitConverter.ToInt64(backingBuffer, pos); 438 | } 439 | 440 | /// 441 | /// Writes a to this Packet. 442 | /// 443 | /// value to write 444 | public void WriteByte(byte value) 445 | { 446 | PreWrite(sizeof(byte)); 447 | backingBuffer[pos] = value; 448 | pos += sizeof(byte); 449 | } 450 | 451 | /// 452 | /// Writes value to this Packet. 453 | /// 454 | /// value to write 455 | public void WriteBool(bool value) 456 | { 457 | PreWrite(sizeof(bool)); 458 | backingBuffer[pos] = value ? (byte)1 : (byte)0; 459 | pos += sizeof(bool); 460 | } 461 | 462 | /// 463 | /// Writes value to this Packet. 464 | /// 465 | /// value to write 466 | public unsafe void WriteInt16(short value) 467 | { 468 | PreWrite(sizeof(short)); 469 | fixed (byte* ptr = backingBuffer) 470 | { 471 | *(short*)(ptr + pos) = value; 472 | } 473 | pos += sizeof(short); 474 | } 475 | 476 | /// 477 | /// Writes value to this Packet. 478 | /// 479 | /// value to write 480 | public unsafe void WriteUInt16(ushort value) 481 | { 482 | PreWrite(sizeof(ushort)); 483 | fixed (byte* ptr = backingBuffer) 484 | { 485 | *(ushort*)(ptr + pos) = value; 486 | } 487 | pos += sizeof(ushort); 488 | } 489 | 490 | /// 491 | /// Writes value to this Packet at the index without modifying the 492 | /// current position to perform future read and write from. 493 | /// 494 | /// value to write 495 | /// index in underlying buffer for this packet to start write at 496 | public unsafe void WriteUInt16At(ushort value, int index) 497 | { 498 | if (index < 0 || (index + sizeof(ushort) > count)) 499 | throw new ArgumentOutOfRangeException("index"); 500 | 501 | fixed (byte* ptr = backingBuffer) 502 | { 503 | *(ushort*)(ptr + (offset + index)) = value; 504 | } 505 | } 506 | 507 | /// 508 | /// Writes value to this Packet. 509 | /// 510 | /// value to write 511 | public unsafe void WriteInt32(int value) 512 | { 513 | PreWrite(sizeof(int)); 514 | fixed (byte* ptr = backingBuffer) 515 | { 516 | *(int*)(ptr + pos) = value; 517 | } 518 | pos += sizeof(int); 519 | } 520 | 521 | /// 522 | /// Writes value to this Packet. 523 | /// 524 | /// value to write 525 | public unsafe void WriteUInt32(uint value) 526 | { 527 | PreWrite(sizeof(uint)); 528 | fixed (byte* ptr = backingBuffer) 529 | { 530 | *(uint*)(ptr + pos) = value; 531 | } 532 | pos += sizeof(uint); 533 | } 534 | 535 | /// 536 | /// Writes value to this Packet. 537 | /// 538 | /// value to write 539 | public unsafe void WriteInt64(long value) 540 | { 541 | PreWrite(sizeof(long)); 542 | fixed (byte* ptr = backingBuffer) 543 | { 544 | *(long*)(ptr + pos) = value; 545 | } 546 | pos += sizeof(long); 547 | } 548 | 549 | /// 550 | /// Writes value to this Packet. 551 | /// 552 | /// value to write 553 | public unsafe void WriteUInt64(ulong value) 554 | { 555 | PreWrite(sizeof(ulong)); 556 | fixed (byte* ptr = backingBuffer) 557 | { 558 | *(ulong*)(ptr + pos) = value; 559 | } 560 | pos += sizeof(ulong); 561 | } 562 | 563 | /// 564 | /// Writes value to this Packet. 565 | /// 566 | /// value to write 567 | public unsafe void WriteSingle(float value) 568 | { 569 | PreWrite(sizeof(float)); 570 | fixed (byte* ptr = backingBuffer) 571 | { 572 | *(float*)(ptr + pos) = value; 573 | } 574 | pos += sizeof(float); 575 | } 576 | 577 | /// 578 | /// Writes value to this Packet. 579 | /// 580 | /// value to write 581 | public unsafe void WriteDouble(double value) 582 | { 583 | PreWrite(sizeof(double)); 584 | fixed (byte* ptr = backingBuffer) 585 | { 586 | *(double*)(ptr + pos) = value; 587 | } 588 | pos += sizeof(double); 589 | } 590 | 591 | /// 592 | /// Writes bytes from in 593 | /// to this Packet. 594 | /// 595 | /// Byte array containing bytes to write. 596 | /// Index in to start writing from. 597 | /// Number of bytes to write. 598 | public void WriteBytes(byte[] bytes, int srcOffset, int count) 599 | { 600 | PreWrite(count); 601 | Buffer.BlockCopy(bytes, srcOffset, backingBuffer, pos, count); 602 | pos += count; 603 | } 604 | 605 | /// 606 | /// Writes bytes from in 607 | /// to this Packet. 608 | /// 609 | /// to copy the bytes from. 610 | /// Index in index to start copying bytes from. 611 | /// Number of bytes to write. 612 | public void WriteBytes(Packet srcPacket, int srcOffset, int count) 613 | { 614 | PreWrite(count); 615 | Buffer.BlockCopy(srcPacket.backingBuffer, srcPacket.offset + srcOffset, backingBuffer, pos, count); 616 | pos += count; 617 | } 618 | 619 | /// 620 | /// Writes all bytes in byte array to this Packet. 621 | /// 622 | /// Byte array to write. 623 | public void WriteBytes(byte[] bytes) 624 | { 625 | if (bytes == null) 626 | return; 627 | WriteBytes(bytes, 0, bytes.Length); 628 | } 629 | 630 | /// 631 | /// Writes string to this Packet encoded using 632 | /// 633 | /// to write. 634 | /// to encode as. 635 | public void WriteString(string value, Encoding encoding) 636 | { 637 | byte[] bytes = encoding.GetBytes(value); 638 | WriteBytes(bytes); 639 | } 640 | 641 | /// 642 | /// Writes size of string in bytes encoded using 643 | /// as a then writes string . 644 | /// 645 | /// to write. 646 | /// to encode as. 647 | public void WriteStringPrefixSize(string value, Encoding encoding) 648 | { 649 | if (String.IsNullOrEmpty(value)) 650 | { 651 | WriteUInt16(0); 652 | } 653 | else 654 | { 655 | byte[] bytes = encoding.GetBytes(value); 656 | WriteUInt16((ushort)bytes.Length); 657 | WriteBytes(bytes); 658 | } 659 | } 660 | 661 | /// 662 | /// Writes a to this Packet. 663 | /// 664 | /// to write. 665 | public void WriteIPEndPoint(IPEndPoint ipEndPoint) 666 | { 667 | #if WINDOWS_UWP 668 | WriteInt64(ipEndPoint.Address.GetAddressAsLong()); 669 | #else 670 | #pragma warning disable 0618 671 | WriteInt64(ipEndPoint.Address.Address); 672 | #pragma warning restore 0618 673 | #endif 674 | WriteUInt16((ushort)ipEndPoint.Port); 675 | } 676 | 677 | /// 678 | /// Writes to this Packet as 16 bytes. 679 | /// 680 | /// value to write. 681 | public void WriteGuid(Guid guid) 682 | { 683 | WriteBytes(guid.ToByteArray()); 684 | } 685 | 686 | /// 687 | /// Writes to 1 to 5 bytes depending on value. 688 | /// 689 | /// to write 690 | /// 691 | /// 692 | /// The number of bytes the value will take: 693 | /// 0 - 127 : 1 byte 694 | /// 128 - 16383 : 2 bytes 695 | /// 16384 - 2097151 : 3 bytes 696 | /// 2097152 - 268435455 : 4 bytes 697 | /// 268435456 - 2147483647 : 5 bytes 698 | /// 699 | /// 700 | public void WriteVariableLengthInt32(int value) 701 | { 702 | if(value < 0) 703 | throw new ArgumentOutOfRangeException("value", "value cannot be less than 0"); 704 | 705 | do 706 | { 707 | byte currentByte = (byte) (value & 0x7F); 708 | value >>= 7; 709 | if (value > 0) 710 | currentByte |= 128; 711 | WriteByte(currentByte); 712 | } while (value > 0); 713 | } 714 | } 715 | } 716 | -------------------------------------------------------------------------------- /src/PacketPool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System; 4 | 5 | namespace FalconUDP 6 | { 7 | /// 8 | /// Pool will grow if exhausted by the same size as inital one however it is best to avoid this 9 | /// by predicting the max size needed. Once expanded the pool never shrinks. 10 | /// 11 | /// Uses a single large buffers which can be divided up and assigned to Packets. This 12 | /// enables buffers to be easily reused and guards against fragmenting heap memory. 13 | /// 14 | internal class PacketPool 15 | { 16 | private readonly List bigBackingBuffers; 17 | private readonly Stack pool; 18 | private readonly int buffersSize; 19 | private readonly int packetsPerBigBuffer; 20 | #if DEBUG 21 | private readonly List leased; 22 | #endif 23 | 24 | internal PacketPool(int buffersSize, int initalNum) 25 | { 26 | this.buffersSize = buffersSize; 27 | this.pool = new Stack(initalNum); 28 | this.bigBackingBuffers = new List(); 29 | this.packetsPerBigBuffer = initalNum; 30 | #if DEBUG 31 | leased = new List(); 32 | #endif 33 | GrowPool(); 34 | } 35 | 36 | private void GrowPool() 37 | { 38 | byte[] bigBackingBuffer = new byte[buffersSize * packetsPerBigBuffer]; 39 | for (int i = 0; i < packetsPerBigBuffer; i++) 40 | { 41 | Packet p = new Packet(bigBackingBuffer, i * buffersSize, buffersSize); 42 | this.pool.Push(p); 43 | } 44 | bigBackingBuffers.Add(bigBackingBuffer); 45 | } 46 | 47 | internal Packet Borrow() 48 | { 49 | if (pool.Count == 0) 50 | { 51 | Debug.WriteLine("***PacketPool depleted, newing up another pool, each pool is {0}bytes.***", buffersSize * packetsPerBigBuffer); 52 | GrowPool(); 53 | } 54 | 55 | Packet p = pool.Pop(); 56 | p.Init(); 57 | #if DEBUG 58 | leased.Add(p); 59 | #endif 60 | return p; 61 | } 62 | 63 | internal void Return(Packet p) 64 | { 65 | #if DEBUG 66 | if (!leased.Contains(p)) 67 | throw new InvalidOperationException("item not leased"); 68 | leased.Remove(p); 69 | #endif 70 | pool.Push(p); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/PingDetail.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace FalconUDP 4 | { 5 | internal class PingDetail 6 | { 7 | internal int? PeerIdPingSentTo; //} 8 | internal IPEndPoint IPEndPointPingSentTo; //} only one set 9 | internal float EllapsedSeconds; 10 | internal bool IsForConnectedPeer { get { return PeerIdPingSentTo.HasValue; } } 11 | 12 | private void Init() 13 | { 14 | this.EllapsedSeconds = 0.0f; 15 | } 16 | 17 | internal void Init(IPEndPoint ipEndPoint) 18 | { 19 | IPEndPointPingSentTo = ipEndPoint; 20 | PeerIdPingSentTo = null; 21 | Init(); 22 | } 23 | 24 | internal void Init(int? peerId) 25 | { 26 | PeerIdPingSentTo = peerId; 27 | IPEndPointPingSentTo = null; 28 | Init(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/PoolSizes.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace FalconUDP 3 | { 4 | /// 5 | /// Class contining the number of objects FalconPeer will create on construction to mitigate 6 | /// later allocations and 7 | /// 8 | public class FalconPoolSizes 9 | { 10 | /// 11 | /// Number of s to pool. The optimal number is the maximum number in 12 | /// use at any one time - which will be the number received between calls to . 13 | /// 14 | public int InitalNumPacketsToPool = 64; 15 | /// 16 | /// Number of Pings to pool - an internal object used whenever a Ping is sent. The optimal 17 | /// number is the maximum in use at any one time - which will be the number of outstanding 18 | /// (i.e. haven't received Pong reply or timed-out) pings. 19 | /// 20 | public int InitalNumPingsToPool = 10; 21 | /// 22 | /// Number of DiscoverySignalTask's to pool - an internal object used to track a discovery 23 | /// process initated from calling one of the Discover or Punch through methods on 24 | /// FalconPeer. The optimal number is the maximum number of discovery process you will 25 | /// have outstanding at any one time. 26 | /// 27 | public int InitalNumEmitDiscoverySignalTaskToPool = 5; 28 | /// 29 | /// Number of internal buffers to pool used to store packets enqued but not yet sent per 30 | /// peer. The optimal number is the maximum number of unsent datagrams at any time which 31 | /// is: one per channel (i.e. 4) plus the number of packets enqueued to send minus the 32 | /// number that can be "packed-in" existing datagrams. 33 | /// 34 | public int InitalNumSendDatagramsToPoolPerPeer = 8; 35 | /// 36 | /// Number of interal data structures use to save ACKknowledgement details to be sent in 37 | /// response to receiving a Reliable message. The optimal number is the maximum number of 38 | /// ACKs oustanding resulting from incoming reliable packets between calls to 39 | /// and 40 | /// 41 | public int InitalNumAcksToPool = 20; 42 | 43 | /// 44 | /// Default pool sizes. 45 | /// 46 | public static readonly FalconPoolSizes Default = new FalconPoolSizes 47 | { 48 | InitalNumPacketsToPool = 64, 49 | InitalNumPingsToPool = 10, 50 | InitalNumEmitDiscoverySignalTaskToPool = 5, 51 | InitalNumSendDatagramsToPoolPerPeer = 40, 52 | InitalNumAcksToPool = 20, 53 | }; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/QualityOfService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace FalconUDP 7 | { 8 | /// 9 | /// Provides QoS estimates for remote peer. 10 | /// 11 | public class QualityOfService 12 | { 13 | private const int ReCalcLatencyAt = 100; 14 | 15 | internal static readonly QualityOfService ZeroedOutQualityOfService = new QualityOfService(); 16 | 17 | private readonly double[] roundTripTimes; 18 | private readonly float resendSampleSizeAsFloat; 19 | private readonly bool[] wasSendResentSample; 20 | 21 | private bool hasUpdateLatencyBeenCalled; 22 | private int roundTripTimesIndex; 23 | private double runningRTTTotal; 24 | private int updateLatencyCountSinceRecalc; 25 | private int wasResentIndex; 26 | private int resentInSampleCount; 27 | 28 | /// 29 | /// The current average round trip time from last 30 | /// reliable messages to till receiving their corresponding ACKnowledgment. 31 | /// 32 | public TimeSpan RoudTripTime { get; private set; } 33 | 34 | /// 35 | /// The current number of messages that had to be re-sent per the last 36 | /// reliable sends (as they were not ACKnowledged in time). 37 | /// 38 | public float ResendRatio { get; private set; } 39 | 40 | /// 41 | /// Flag set when reliable packets are currently having to be re-sent as not ACKnowledged 42 | /// in time. When true indicates poor network conditions which may be only temporary. 43 | /// 44 | public bool IsNotResponding { get; internal set; } 45 | 46 | private QualityOfService() 47 | { } 48 | 49 | internal QualityOfService(byte latencySampleSize, ushort resendRatioSampleLength) 50 | { 51 | this.roundTripTimes = new double[latencySampleSize]; 52 | this.wasSendResentSample = new bool[resendRatioSampleLength]; 53 | this.resendSampleSizeAsFloat = (float)resendRatioSampleLength; 54 | } 55 | 56 | internal void UpdateLatency(TimeSpan rtt) 57 | { 58 | double seconds = rtt.TotalSeconds; 59 | 60 | updateLatencyCountSinceRecalc++; 61 | 62 | // If this is the first time this is being called seed entire sample with inital value 63 | // and set latency to RTT, it's all we have! 64 | if (!hasUpdateLatencyBeenCalled) 65 | { 66 | for (int i = 0; i < roundTripTimes.Length; i++) 67 | { 68 | roundTripTimes[i] = seconds; 69 | } 70 | runningRTTTotal = rtt.TotalSeconds * roundTripTimes.Length; 71 | RoudTripTime = rtt; 72 | hasUpdateLatencyBeenCalled = true; 73 | } 74 | else 75 | { 76 | if (updateLatencyCountSinceRecalc == ReCalcLatencyAt) 77 | { 78 | roundTripTimes[roundTripTimesIndex] = seconds; // replace oldest RTT in sample with new RTT 79 | 80 | // Recalc running total from all in sample to remove any drift introduced. 81 | runningRTTTotal = 0.0f; 82 | foreach (double roundTripTime in roundTripTimes) 83 | { 84 | runningRTTTotal += roundTripTime; 85 | } 86 | updateLatencyCountSinceRecalc = 0; 87 | } 88 | else 89 | { 90 | runningRTTTotal -= roundTripTimes[roundTripTimesIndex]; // subtract oldest RTT from running total 91 | roundTripTimes[roundTripTimesIndex] = seconds; // replace oldest RTT in sample with new RTT 92 | runningRTTTotal += seconds; // add new RTT to running total 93 | } 94 | 95 | RoudTripTime = TimeSpan.FromSeconds(runningRTTTotal / roundTripTimes.Length); // re-calc average RTT from running total 96 | } 97 | 98 | // increment index for next time this is called 99 | roundTripTimesIndex++; 100 | if (roundTripTimesIndex == roundTripTimes.Length) 101 | { 102 | roundTripTimesIndex = 0; 103 | } 104 | } 105 | 106 | internal void UpdateResentSample(bool wasResent) 107 | { 108 | // update sample 109 | wasSendResentSample[wasResentIndex] = wasResent; 110 | wasResentIndex++; 111 | if (wasResentIndex == wasSendResentSample.Length) 112 | wasResentIndex = 0; 113 | 114 | // update running count of sample that were re-sent 115 | if (wasResent) 116 | resentInSampleCount++; 117 | 118 | ResendRatio = resentInSampleCount / resendSampleSizeAsFloat; 119 | 120 | // wasReSentIndex now points to oldest sample remove it from count before replaced 121 | // next time this is called. 122 | if (wasSendResentSample[wasResentIndex]) 123 | resentInSampleCount--; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/ReceiveChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace FalconUDP 5 | { 6 | internal class ReceiveChannel 7 | { 8 | private readonly SortedList receivedPackets; 9 | private readonly List datagramsSeqRecentlyRead; 10 | private readonly List packetsRead; 11 | private readonly SendOptions channelType; 12 | private readonly FalconPeer localPeer; 13 | private readonly RemotePeer remotePeer; 14 | private readonly bool isReliable; 15 | private readonly bool isInOrder; 16 | private readonly int numberOfDatagramSequencesReadToKeep; 17 | private float lastReceivedPacketSeq; 18 | private int maxReadDatagramSeq; 19 | 20 | 21 | /// 22 | /// Number of unread packets ready for reading 23 | /// 24 | internal int Count { get; private set; } 25 | 26 | internal ReceiveChannel(SendOptions channelType, FalconPeer localPeer, RemotePeer remotePeer) 27 | { 28 | this.channelType = channelType; 29 | this.localPeer = localPeer; 30 | this.remotePeer = remotePeer; 31 | this.isReliable = (channelType & SendOptions.Reliable) == SendOptions.Reliable; 32 | this.isInOrder = (channelType & SendOptions.InOrder) == SendOptions.InOrder; 33 | this.receivedPackets = new SortedList(); 34 | this.packetsRead = new List(); 35 | this.numberOfDatagramSequencesReadToKeep = localPeer.MaxOutOfOrderTolerence * 2; 36 | this.datagramsSeqRecentlyRead = new List(this.numberOfDatagramSequencesReadToKeep); 37 | } 38 | 39 | // returns true if datagram is valid, otherwise it should be dropped any additional packets in it should not be processed 40 | internal bool TryAddReceivedPacket( 41 | ushort datagramSeq, 42 | PacketType type, 43 | byte[] buffer, 44 | int index, 45 | int count, 46 | bool isFirstPacketInDatagram, 47 | out bool applicationPacketAdded) 48 | { 49 | applicationPacketAdded = false; // until proven otherwise 50 | 51 | float ordinalPacketSeq; 52 | 53 | if (isFirstPacketInDatagram) 54 | { 55 | // If datagram requries ACK - send it! Do this before checking not duplicate or in- 56 | // order as peer could be re-sending as it never got ACK. 57 | if (isReliable) 58 | { 59 | remotePeer.ACK(datagramSeq, channelType); 60 | } 61 | 62 | // TODO Needs a re-think as doing this means if application is sending fast enough 63 | // on a Reliable channel re-sending a packet that got dropped will be far 64 | // behind subsequent sends will be out-of-range by this check so repetadly be 65 | // dropped and eventually cause the remote peer to drop us. The reason we are 66 | // doing this is so we know when we can reset ordinalSeq and 67 | // maxReadDatagramSeq. For now set default to 100 (previously was 8) to 68 | // mitigate issue. 69 | 70 | // validate seq in range 71 | 72 | ushort min = unchecked((ushort)(lastReceivedPacketSeq - localPeer.OutOfOrderTolerance)); 73 | ushort max = unchecked((ushort)(lastReceivedPacketSeq + localPeer.OutOfOrderTolerance)); 74 | 75 | // NOTE: Max could be less than min if exceeded MaxValue, likewise min could be 76 | // greater than max if less than 0. So have to check seq between min - max range 77 | // which is a loop, inclusive. 78 | 79 | if (datagramSeq > max && datagramSeq < min) 80 | { 81 | localPeer.Log(LogLevel.Warning, String.Format("Out-of-order packet from: {0} dropped, out-of-order from last by: {1}.", remotePeer.PeerName, datagramSeq - lastReceivedPacketSeq)); 82 | return false; 83 | } 84 | 85 | // validate not duplicate 86 | 87 | if (datagramsSeqRecentlyRead.Contains(datagramSeq)) 88 | { 89 | localPeer.Log(LogLevel.Warning, String.Format("Duplicate datagram from: {0} dropped.", remotePeer.PeerName)); 90 | return false; 91 | } 92 | 93 | if (datagramsSeqRecentlyRead.Count == numberOfDatagramSequencesReadToKeep) 94 | { 95 | datagramsSeqRecentlyRead.RemoveAt(0); 96 | } 97 | datagramsSeqRecentlyRead.Add(datagramSeq); 98 | 99 | ordinalPacketSeq = datagramSeq; 100 | int diff = Math.Abs(datagramSeq - (int)lastReceivedPacketSeq); 101 | if (diff > localPeer.OutOfOrderTolerance) 102 | { 103 | if (datagramSeq < lastReceivedPacketSeq) // i.e. seq must have looped since we have already validated seq in range 104 | { 105 | ordinalPacketSeq += ushort.MaxValue; 106 | } 107 | } 108 | 109 | // if datagram required to be in order check after max read, if not drop it 110 | if (isInOrder) 111 | { 112 | if (ordinalPacketSeq < maxReadDatagramSeq) 113 | { 114 | return false; 115 | } 116 | } 117 | } 118 | else // i.e. additional packet 119 | { 120 | // lastReceived Seq will be ordinal seq for previous packet in datagram 121 | ordinalPacketSeq = lastReceivedPacketSeq + 0.01f; 122 | } 123 | 124 | lastReceivedPacketSeq = ordinalPacketSeq; 125 | 126 | if (type == PacketType.KeepAlive) 127 | { 128 | if (!remotePeer.IsKeepAliveMaster) 129 | { 130 | // To have received a KeepAlive from this peer who is not the KeepAlive master 131 | // is only valid when the peer never received a KeepAlive from us for 132 | // FalconPeer.KeepAliveProbeInterval for which the most common cause would be 133 | // we disappered though we must be back up again to have received it! 134 | 135 | localPeer.Log(LogLevel.Warning, String.Format("Received KeepAlive from: {0} who's not the KeepAlive master!", remotePeer.PeerName)); 136 | } 137 | 138 | // nothing else to do - would have already ACKd this datagram 139 | 140 | return true; 141 | } 142 | 143 | if (type == PacketType.AcceptJoin) 144 | { 145 | // Probably our ACK did not get through so the remote peer is re-sending, 146 | // nothing else to do - would have already ACKd this datagram. 147 | 148 | return true; 149 | } 150 | 151 | // Must be Application packet 152 | 153 | Packet packet = localPeer.PacketPool.Borrow(); 154 | packet.WriteBytes(buffer, index, count); 155 | packet.ResetAndMakeReadOnly(remotePeer.Id); 156 | packet.DatagramSeq = datagramSeq; 157 | 158 | // Add packet 159 | receivedPackets.Add(ordinalPacketSeq, packet); 160 | 161 | if (isReliable) 162 | { 163 | // re-calc number of continuous seq from first 164 | Count = 1; 165 | int key = receivedPackets[receivedPackets.Keys[0]].DatagramSeq; 166 | for (int i = 1; i < receivedPackets.Count; i++) 167 | { 168 | int next = receivedPackets[receivedPackets.Keys[i]].DatagramSeq; 169 | if (next == key) 170 | { 171 | // NOTE: This must be an additional packet with the same 172 | // datagram seq. 173 | 174 | Count++; 175 | } 176 | else if (next == (key + 1)) 177 | { 178 | Count++; 179 | key = next; 180 | } 181 | else 182 | { 183 | break; 184 | } 185 | } 186 | } 187 | else 188 | { 189 | Count++; 190 | } 191 | 192 | applicationPacketAdded = true; 193 | 194 | return true; 195 | } 196 | 197 | internal List Read() 198 | { 199 | packetsRead.Clear(); 200 | 201 | if (Count > 0) 202 | { 203 | if (isReliable) 204 | { 205 | while (Count > 0) 206 | { 207 | maxReadDatagramSeq = receivedPackets[receivedPackets.Keys[receivedPackets.Count - 1]].DatagramSeq; 208 | packetsRead.Add(receivedPackets[receivedPackets.Keys[0]]); 209 | receivedPackets.RemoveAt(0); 210 | Count--; 211 | } 212 | } 213 | else 214 | { 215 | packetsRead.Capacity = receivedPackets.Count; 216 | packetsRead.AddRange(receivedPackets.Values); 217 | maxReadDatagramSeq = receivedPackets[receivedPackets.Keys[receivedPackets.Count - 1]].DatagramSeq; 218 | receivedPackets.Clear(); 219 | Count = 0; 220 | } 221 | 222 | // If max read seq > (ushort.MaxValue + FalconPeer.OutOfOrderTolerance) no future 223 | // datagram will be from the old loop (without being dropped), so reset max and 224 | // ordinal seq to the same value as seq they are for. 225 | 226 | if (maxReadDatagramSeq > localPeer.MaxNeededOrindalSeq) 227 | { 228 | maxReadDatagramSeq -= ushort.MaxValue; 229 | lastReceivedPacketSeq -= ushort.MaxValue; 230 | } 231 | } 232 | 233 | return packetsRead; 234 | } 235 | 236 | public void ReturnLeasedPackets() 237 | { 238 | var packets = Read(); 239 | foreach (Packet packet in packets) 240 | localPeer.ReturnPacketToPool(packet); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/RemotePeer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | #if NETFX_CORE 5 | #else 6 | using System.Net.Sockets; 7 | #endif 8 | 9 | namespace FalconUDP 10 | { 11 | internal class RemotePeer 12 | { 13 | private readonly bool keepAliveAndAutoFlush; 14 | private readonly FalconPeer localPeer; // local peer this remote peer has joined 15 | private readonly DatagramPool sendDatagramsPool; 16 | private readonly List sentDatagramsAwaitingACK; 17 | private readonly List delayedDatagrams; 18 | private readonly Queue enqueudAcks; 19 | private readonly List allUnreadPackets; 20 | private readonly QualityOfService qualityOfService; 21 | 22 | private SendChannel noneSendChannel, reliableSendChannel, inOrderSendChannel, reliableInOrderSendChannel; 23 | private ReceiveChannel noneReceiveChannel, reliableReceiveChannel, inOrderReceiveChannel, reliableInOrderReceiveChannel; 24 | private int unreadPacketCount; 25 | private IPEndPoint endPoint; 26 | private float ellapasedSecondsSinceLastRealiablePacket; // if this remote peer is the keep alive master; this is since last reliable packet sent to it, otherwise since the last reliable received from it 27 | private float ellapsedSecondsSinceSendQueuesLastFlushed; 28 | private bool hasAccepted; // We have recieved an ACK from this peer (the first ACK if in response to Accept) 29 | 30 | internal bool IsKeepAliveMaster; // i.e. this remote peer is the master so it will send the KeepAlives, not us! 31 | internal int Id { get; private set; } 32 | internal IPEndPoint EndPoint { get { return endPoint; } } 33 | internal int UnreadPacketCount { get { return unreadPacketCount; } } // number of received packets not yet read by application 34 | internal string PeerName { get; private set; } // e.g. IP end point, used for logging 35 | internal TimeSpan RoundTripTime { get { return qualityOfService.RoudTripTime; } } 36 | internal QualityOfService QualityOfService { get { return qualityOfService; } } 37 | 38 | internal RemotePeer(FalconPeer localPeer, IPEndPoint endPoint, int peerId, bool keepAliveAndAutoFlush = true) 39 | { 40 | this.Id = peerId; 41 | this.localPeer = localPeer; 42 | this.endPoint = endPoint; 43 | #if DEBUG 44 | this.PeerName = endPoint.ToString(); 45 | #else 46 | this.PeerName = String.Empty; 47 | #endif 48 | this.unreadPacketCount = 0; 49 | this.sendDatagramsPool = new DatagramPool(FalconPeer.MaxDatagramSize, localPeer.PoolSizes.InitalNumSendDatagramsToPoolPerPeer); 50 | this.sentDatagramsAwaitingACK = new List(); 51 | this.qualityOfService = new QualityOfService(localPeer.LatencySampleSize, localPeer.ResendRatioSampleSize); 52 | 53 | this.delayedDatagrams = new List(); 54 | this.keepAliveAndAutoFlush = keepAliveAndAutoFlush; 55 | this.allUnreadPackets = new List(); 56 | this.enqueudAcks = new Queue(); 57 | 58 | CreateSendRecieveChannels(); 59 | } 60 | 61 | private void CreateSendRecieveChannels() 62 | { 63 | this.noneSendChannel = new SendChannel(SendOptions.None, sendDatagramsPool); 64 | this.inOrderSendChannel = new SendChannel(SendOptions.InOrder, sendDatagramsPool); 65 | this.reliableSendChannel = new SendChannel(SendOptions.Reliable, sendDatagramsPool); 66 | this.reliableInOrderSendChannel = new SendChannel(SendOptions.ReliableInOrder, sendDatagramsPool); 67 | this.noneReceiveChannel = new ReceiveChannel(SendOptions.None, this.localPeer, this); 68 | this.inOrderReceiveChannel = new ReceiveChannel(SendOptions.InOrder, this.localPeer, this); 69 | this.reliableReceiveChannel = new ReceiveChannel(SendOptions.Reliable, this.localPeer, this); 70 | this.reliableInOrderReceiveChannel = new ReceiveChannel(SendOptions.ReliableInOrder, this.localPeer, this); 71 | } 72 | 73 | private bool TrySendDatagram(Datagram datagram, bool hasAlreadyBeenDelayed = false) 74 | { 75 | // If we are the keep alive master (i.e. this remote peer is not) and this 76 | // packet is reliable: update ellpasedMilliseondsAtLastRealiablePacket[Sent] 77 | if (!IsKeepAliveMaster && datagram.IsReliable) 78 | { 79 | ellapasedSecondsSinceLastRealiablePacket = 0.0f; 80 | } 81 | 82 | // simulate packet loss 83 | if (localPeer.SimulatePacketLossProbability > 0.0) 84 | { 85 | if (SingleRandom.NextDouble() < localPeer.SimulatePacketLossProbability) 86 | { 87 | localPeer.Log(LogLevel.Info, String.Format("DROPPED packet to send - simulate packet loss set at: {0}", localPeer.SimulatePacketLossChance)); 88 | return true; 89 | } 90 | } 91 | 92 | // if reliable and not already delayed update time sent used to measure RTT when ACK response received 93 | if (datagram.IsReliable && !hasAlreadyBeenDelayed) 94 | { 95 | datagram.EllapsedAtSent = localPeer.Stopwatch.Elapsed; 96 | } 97 | 98 | // simulate delay 99 | if (localPeer.SimulateLatencySeconds > 0.0f 100 | && !hasAlreadyBeenDelayed 101 | && datagram.Type != PacketType.Bye) // if Bye we don't delay as if closing will not be sent 102 | { 103 | float delay = localPeer.SimulateLatencySeconds; 104 | 105 | // jitter 106 | if (localPeer.SimulateJitterSeconds > 0.0f) 107 | { 108 | float jitter = localPeer.SimulateJitterSeconds * (float)SingleRandom.NextDouble(); 109 | if (SingleRandom.NextDouble() < 0.5) 110 | jitter *= -1; 111 | 112 | delay += jitter; 113 | } 114 | 115 | DelayedDatagram delayedDatagram = new DelayedDatagram(delay, datagram); 116 | 117 | localPeer.Log(LogLevel.Debug, String.Format("...DELAYED Sending datagram to: {0}, seq {1}, channel: {2}, total size: {3}; by {4}s...", 118 | endPoint.ToString(), 119 | datagram.Sequence.ToString(), 120 | datagram.SendOptions.ToString(), 121 | datagram.Count.ToString(), 122 | delayedDatagram.EllapsedSecondsRemainingToDelay.ToString())); 123 | 124 | delayedDatagrams.Add(delayedDatagram); 125 | 126 | return true; 127 | } 128 | 129 | localPeer.Log(LogLevel.Debug, String.Format("--> Sending datagram to: {0}, seq {1}, channel: {2}, total size: {3}...", 130 | endPoint.ToString(), 131 | datagram.Sequence.ToString(), 132 | datagram.SendOptions.ToString(), 133 | datagram.Count.ToString())); 134 | 135 | // Expedite send if less than 5% of recent reliable packets have to be re-sent 136 | bool expedite = qualityOfService.ResendRatio < 0.05f; 137 | 138 | //----------------------------------------------------------------------------------------------------------------- 139 | bool success = localPeer.Transceiver.Send(datagram.BackingBuffer, datagram.Offset, datagram.Count, endPoint, expedite); 140 | //----------------------------------------------------------------------------------------------------------------- 141 | 142 | if (success) 143 | { 144 | if (localPeer.IsCollectingStatistics) 145 | { 146 | localPeer.Statistics.AddBytesSent(datagram.Count); 147 | } 148 | 149 | // return the datagram to pool for re-use if we are not waiting for an ACK 150 | if (!datagram.IsReliable) 151 | { 152 | sendDatagramsPool.Return(datagram); 153 | } 154 | } 155 | else 156 | { 157 | // something fatal has gone wrong and this peer can no longer be sent to 158 | localPeer.RemovePeerOnNextUpdate(this); 159 | } 160 | 161 | return success; 162 | } 163 | 164 | // writes as many enqued as as can fit into datagram 165 | private void WriteEnquedAcksToDatagram(Datagram datagram, int index) 166 | { 167 | while (enqueudAcks.Count > 0 && (datagram.MaxSize - (index - datagram.Offset)) > Const.FALCON_PACKET_HEADER_SIZE) 168 | { 169 | AckDetail ack = enqueudAcks.Dequeue(); 170 | FalconHelper.WriteAck(ack, datagram.BackingBuffer, index); 171 | index += Const.FALCON_PACKET_HEADER_SIZE; 172 | } 173 | datagram.Resize(index - datagram.Offset); 174 | } 175 | 176 | private bool TryFlushSendChannel(SendChannel channel) 177 | { 178 | if (!channel.HasDataToSend) 179 | return true; 180 | 181 | Queue queue = channel.GetQueue(); 182 | 183 | while (queue.Count > 0) 184 | { 185 | Datagram datagram = queue.Dequeue(); 186 | 187 | if (channel.IsReliable) 188 | { 189 | if (datagram.IsResend) 190 | { 191 | // update the time sent 192 | datagram.EllapsedSecondsSincePacketSent = 0.0f; 193 | } 194 | else // i.e. not a re-send 195 | { 196 | // Save to this reliable send to measure ACK response time and 197 | // in case needs re-sending. 198 | sentDatagramsAwaitingACK.Add(datagram); 199 | } 200 | } 201 | 202 | // append any ACKs awaiting to be sent that will fit in datagram 203 | if (enqueudAcks.Count > 0) 204 | { 205 | WriteEnquedAcksToDatagram(datagram, datagram.Offset + datagram.Count); 206 | } 207 | 208 | bool success = TrySendDatagram(datagram); 209 | if (!success) 210 | return false; 211 | 212 | } // while 213 | 214 | return true; 215 | } 216 | 217 | private bool Pong() 218 | { 219 | EnqueueSend(PacketType.Pong, SendOptions.None, null); 220 | return ForceFlushSendChannelNow(SendOptions.None); // pongs must be sent immediatly as RTT is measured 221 | } 222 | 223 | private void DiscoverReply() 224 | { 225 | EnqueueSend(PacketType.DiscoverReply, SendOptions.None, null); 226 | } 227 | 228 | private void ReSend(Datagram datagram) 229 | { 230 | datagram.IsResend = true; 231 | 232 | qualityOfService.UpdateResentSample(true); 233 | 234 | switch (datagram.SendOptions) 235 | { 236 | case SendOptions.Reliable: 237 | reliableSendChannel.EnqueueSend(datagram); 238 | break; 239 | case SendOptions.ReliableInOrder: 240 | reliableInOrderSendChannel.EnqueueSend(datagram); 241 | break; 242 | default: 243 | throw new InvalidOperationException(String.Format("{0} datagrams cannot be re-sent!", datagram.SendOptions)); 244 | } 245 | } 246 | 247 | internal void Update(float dt) 248 | { 249 | // 250 | // Update counters 251 | // 252 | ellapasedSecondsSinceLastRealiablePacket += dt; 253 | ellapsedSecondsSinceSendQueuesLastFlushed += dt; 254 | //// 255 | //// Update enqued ACKs stopover time 256 | //// 257 | //if (enqueudAcks.Count > 0) 258 | //{ 259 | // for(int i = 0; i 0) 273 | { 274 | bool anyResendsAwaitingACK = false; 275 | 276 | for (int i = 0; i < sentDatagramsAwaitingACK.Count; i++) 277 | { 278 | Datagram datagramAwaitingAck = sentDatagramsAwaitingACK[i]; 279 | datagramAwaitingAck.EllapsedSecondsSincePacketSent += dt; 280 | 281 | float ackTimeout = localPeer.GetAckTimeout(datagramAwaitingAck.ResentCount); 282 | 283 | if (datagramAwaitingAck.EllapsedSecondsSincePacketSent >= ackTimeout) 284 | { 285 | datagramAwaitingAck.ResentCount++; 286 | 287 | if (datagramAwaitingAck.ResentCount > localPeer.MaxResends) 288 | { 289 | // give-up, assume the peer has disconnected (without saying bye) and drop it 290 | sentDatagramsAwaitingACK.RemoveAt(i); 291 | i--; 292 | 293 | localPeer.Log(LogLevel.Warning, String.Format("Peer failed to ACK {0} re-sends of Reliable datagram seq {1}, channel {2}. total size: {3}, in time.", 294 | localPeer.MaxResends, 295 | datagramAwaitingAck.Sequence.ToString(), 296 | datagramAwaitingAck.SendOptions.ToString(), 297 | datagramAwaitingAck.Count.ToString())); 298 | 299 | sendDatagramsPool.Return(datagramAwaitingAck); 300 | localPeer.RemovePeerOnNextUpdate(this); 301 | return; 302 | } 303 | else 304 | { 305 | // try again.. 306 | datagramAwaitingAck.EllapsedSecondsSincePacketSent = 0.0f; 307 | ReSend(datagramAwaitingAck); 308 | ellapasedSecondsSinceLastRealiablePacket = 0.0f; // NOTE: this is reset again when packet actually sent but another Update() may occur before then 309 | 310 | localPeer.Log(LogLevel.Info, String.Format("Datagram to: {0}, seq {1}, channel: {2}, total size: {3} re-sent as not ACKnowledged in {4}s.", 311 | PeerName, 312 | datagramAwaitingAck.Sequence.ToString(), 313 | datagramAwaitingAck.SendOptions.ToString(), 314 | datagramAwaitingAck.Count.ToString(), 315 | ackTimeout.ToString())); 316 | } 317 | } 318 | 319 | if (datagramAwaitingAck.IsResend) 320 | anyResendsAwaitingACK = true; 321 | } 322 | 323 | qualityOfService.IsNotResponding = anyResendsAwaitingACK; 324 | } 325 | // 326 | // KeepAlives and AutoFlush 327 | // 328 | if (keepAliveAndAutoFlush) 329 | { 330 | if (IsKeepAliveMaster) // i.e. this remote peer is the keep alive master, not us 331 | { 332 | if (ellapasedSecondsSinceLastRealiablePacket >= localPeer.KeepAliveProbeAfterSeconds) 333 | { 334 | // This remote peer has not sent a reliable message for too long, send a 335 | // KeepAlive to probe them and they are alive! 336 | 337 | reliableSendChannel.EnqueueSend(PacketType.KeepAlive, null); 338 | ellapasedSecondsSinceLastRealiablePacket = 0.0f; 339 | } 340 | } 341 | else if (ellapasedSecondsSinceLastRealiablePacket >= localPeer.KeepAliveIntervalSeconds) 342 | { 343 | reliableSendChannel.EnqueueSend(PacketType.KeepAlive, null); 344 | ellapasedSecondsSinceLastRealiablePacket = 0.0f; // NOTE: this is reset again when packet actually sent but another Update() may occur before then 345 | } 346 | 347 | if (localPeer.AutoFlushIntervalSeconds > 0.0f) 348 | { 349 | if (ellapsedSecondsSinceSendQueuesLastFlushed >= localPeer.AutoFlushIntervalSeconds) 350 | { 351 | localPeer.Log(LogLevel.Info, "AutoFlush"); 352 | bool success = TryFlushSendQueues(); // resets ellapsedSecondsSinceSendQueuesLastFlushed 353 | if(!success) 354 | return; 355 | } 356 | } 357 | } 358 | // 359 | // Simulate Delay 360 | // 361 | if (delayedDatagrams.Count > 0) 362 | { 363 | for (int i = 0; i < delayedDatagrams.Count; i++) 364 | { 365 | DelayedDatagram delayedDatagram = delayedDatagrams[i]; 366 | delayedDatagram.EllapsedSecondsRemainingToDelay -= dt; 367 | if (delayedDatagram.EllapsedSecondsRemainingToDelay <= 0.0f) 368 | { 369 | bool success = TrySendDatagram(delayedDatagram.Datagram, true); 370 | if(!success) 371 | return; 372 | delayedDatagrams.RemoveAt(i); 373 | i--; 374 | } 375 | } 376 | } 377 | } 378 | 379 | internal void UpdateEndPoint(IPEndPoint ip) 380 | { 381 | endPoint = ip; 382 | #if DEBUG 383 | PeerName = ip.ToString(); 384 | #else 385 | // PeerName is not used in Release builds so do don't create the garbage 386 | PeerName = String.Empty; 387 | #endif 388 | } 389 | 390 | internal void EnqueueSend(PacketType type, SendOptions opts, Packet packet) 391 | { 392 | if(packet != null) 393 | packet.IsReadOnly = true; 394 | 395 | switch (opts) 396 | { 397 | case SendOptions.None: 398 | noneSendChannel.EnqueueSend(type, packet); 399 | break; 400 | case SendOptions.InOrder: 401 | inOrderSendChannel.EnqueueSend(type, packet); 402 | break; 403 | case SendOptions.Reliable: 404 | reliableSendChannel.EnqueueSend(type, packet); 405 | break; 406 | case SendOptions.ReliableInOrder: 407 | reliableInOrderSendChannel.EnqueueSend(type, packet); 408 | break; 409 | } 410 | } 411 | 412 | internal void ACK(ushort seq, SendOptions channelType) 413 | { 414 | AckDetail ack = new AckDetail(); 415 | ack.Init(seq, channelType); 416 | enqueudAcks.Enqueue(ack); 417 | } 418 | 419 | internal bool Accept() 420 | { 421 | EnqueueSend(PacketType.AcceptJoin, SendOptions.Reliable, null); 422 | return ForceFlushSendChannelNow(SendOptions.Reliable); 423 | } 424 | 425 | internal void Ping() 426 | { 427 | EnqueueSend(PacketType.Ping, SendOptions.None, null); 428 | } 429 | 430 | internal void Bye() 431 | { 432 | EnqueueSend(PacketType.Bye, SendOptions.None, null); 433 | } 434 | 435 | // used for internal sends that need to be sent immediatly only 436 | internal bool ForceFlushSendChannelNow(SendOptions channelType) 437 | { 438 | SendChannel channel = null; 439 | switch (channelType) 440 | { 441 | case SendOptions.None: channel = noneSendChannel; break; 442 | case SendOptions.InOrder: channel = inOrderSendChannel; break; 443 | case SendOptions.Reliable: channel = reliableSendChannel; break; 444 | case SendOptions.ReliableInOrder: channel = reliableInOrderSendChannel; break; 445 | } 446 | 447 | return TryFlushSendChannel(channel); 448 | } 449 | 450 | internal bool TryFlushSendQueues() 451 | { 452 | bool success = true; 453 | 454 | success = TryFlushSendChannel(noneSendChannel); 455 | if (success) 456 | success = TryFlushSendChannel(inOrderSendChannel); 457 | if(success) 458 | success = TryFlushSendChannel(reliableSendChannel); 459 | if (success) 460 | success = TryFlushSendChannel(reliableInOrderSendChannel); 461 | 462 | // send any outstanding ACKs 463 | if (enqueudAcks.Count > 0) 464 | { 465 | while (enqueudAcks.Count > 0) 466 | { 467 | Datagram datagram = sendDatagramsPool.Borrow(); 468 | datagram.SendOptions = SendOptions.None; 469 | WriteEnquedAcksToDatagram(datagram, datagram.Offset); 470 | success = TrySendDatagram(datagram); 471 | if (!success) 472 | break; 473 | } 474 | } 475 | 476 | ellapsedSecondsSinceSendQueuesLastFlushed = 0.0f; 477 | 478 | return success; 479 | } 480 | 481 | // returns true if caller should continue adding any additional packets in datagram 482 | internal bool TryAddReceivedPacket( 483 | ushort seq, 484 | SendOptions opts, 485 | PacketType type, 486 | byte[] buffer, 487 | int index, 488 | int payloadSize, 489 | bool isFirstPacketInDatagram) 490 | { 491 | // If we are not the keep alive master, i.e. this remote peer is, and this packet was 492 | // sent reliably reset ellpasedMilliseondsAtLastRealiablePacket[Received]. 493 | if (IsKeepAliveMaster && ((opts & SendOptions.Reliable) == SendOptions.Reliable)) 494 | { 495 | ellapasedSecondsSinceLastRealiablePacket = 0.0f; 496 | } 497 | 498 | switch (type) 499 | { 500 | case PacketType.Application: 501 | case PacketType.KeepAlive: 502 | case PacketType.AcceptJoin: 503 | { 504 | bool wasAppPacketAdded; 505 | 506 | ReceiveChannel channel = noneReceiveChannel; 507 | switch (opts) 508 | { 509 | case SendOptions.InOrder: channel = inOrderReceiveChannel; break; 510 | case SendOptions.Reliable: channel = reliableReceiveChannel; break; 511 | case SendOptions.ReliableInOrder: channel = reliableInOrderReceiveChannel; break; 512 | } 513 | 514 | if(!channel.TryAddReceivedPacket(seq, type, buffer, index, payloadSize, isFirstPacketInDatagram, out wasAppPacketAdded)) 515 | return false; 516 | 517 | if (wasAppPacketAdded) 518 | unreadPacketCount++; 519 | 520 | return true; 521 | } 522 | case PacketType.ACK: 523 | { 524 | // Look for the oldest datagram with the same seq AND channel type 525 | // seq is for which we ASSUME the ACK is for. 526 | int datagramIndex; 527 | Datagram sentDatagramAwaitingAck = null; 528 | for (datagramIndex = 0; datagramIndex < sentDatagramsAwaitingACK.Count; datagramIndex++) 529 | { 530 | Datagram datagram = sentDatagramsAwaitingACK[datagramIndex]; 531 | if (datagram.Sequence == seq && datagram.SendOptions == opts) 532 | { 533 | sentDatagramAwaitingAck = datagram; 534 | break; 535 | } 536 | } 537 | 538 | if (sentDatagramAwaitingAck == null) 539 | { 540 | // Possible reasons: 541 | // 1) ACK has arrived too late and the datagram must have already been removed. 542 | // 2) ACK duplicated and has already been processed 543 | // 3) ACK was unsolicited (i.e. malicious or buggy peer) 544 | 545 | // NOTE: If ACK "piggy-backed" on a reliable datagram that was re-sent 546 | // that datagram if recieved more than once would have been dropped 547 | // when processing the first Application packet so should never 548 | // get to here. 549 | 550 | localPeer.Log(LogLevel.Warning, "Datagram for ACK not found - too late?"); 551 | } 552 | else 553 | { 554 | // recieving our first ACK from this peer means they have Accepted us 555 | if (!hasAccepted) 556 | hasAccepted = true; 557 | 558 | // remove datagram 559 | sentDatagramsAwaitingACK.RemoveAt(datagramIndex); 560 | 561 | // Update QoS 562 | // ---------- 563 | // 564 | // If the datagram was not re-sent update latency, otherwise we do not 565 | // know which send this ACK is for so cannot determine latency. 566 | // 567 | // Also update re-sends sample if datagram was not re-sent with: not 568 | // re-sent. If was re-sent sample would have already been updated when 569 | // re-sent. 570 | 571 | if (sentDatagramAwaitingAck.ResentCount == 0) 572 | { 573 | TimeSpan rtt = localPeer.Stopwatch.Elapsed - sentDatagramAwaitingAck.EllapsedAtSent; 574 | qualityOfService.UpdateLatency(rtt); 575 | qualityOfService.UpdateResentSample(false); 576 | 577 | localPeer.Log(LogLevel.Debug, String.Format("ACK from: {0}, channel: {1}, seq: {2}, RTT: {3}s", PeerName, opts.ToString(), seq.ToString(), rtt.TotalSeconds.ToString())); 578 | localPeer.Log(LogLevel.Debug, String.Format("Latency now: {0}s", qualityOfService.RoudTripTime.TotalSeconds.ToString())); 579 | } 580 | else 581 | { 582 | localPeer.Log(LogLevel.Info, String.Format("ACK for re-sent datagram: {0}, channel: {1}, seq: {2}", PeerName, opts.ToString(), seq.ToString())); 583 | } 584 | 585 | 586 | // return datagram 587 | sendDatagramsPool.Return(sentDatagramAwaitingAck); 588 | } 589 | 590 | return true; 591 | } 592 | case PacketType.Ping: 593 | { 594 | return Pong(); 595 | } 596 | case PacketType.Pong: 597 | { 598 | if(localPeer.HasPingsAwaitingPong) 599 | { 600 | PingDetail detail = localPeer.PingsAwaitingPong.Find(pd => pd.PeerIdPingSentTo == Id); 601 | if(detail != null) 602 | { 603 | localPeer.RaisePongReceived(this, TimeSpan.FromSeconds(detail.EllapsedSeconds)); 604 | localPeer.RemovePingAwaitingPongDetail(detail); 605 | } 606 | } 607 | 608 | return true; 609 | } 610 | case PacketType.JoinRequest: 611 | { 612 | if (hasAccepted) 613 | { 614 | // If peer has accepted must be is joining again (possible when did not 615 | // say Bye and has restarted again before timed out). Drop this 616 | // instance of peer and process Accept next update so both peers on 617 | // the same page, i.e. that we are starting a new connection. 618 | // 619 | byte[] datagram = new byte[Const.FALCON_PACKET_HEADER_SIZE + payloadSize]; 620 | FalconHelper.WriteFalconHeader(datagram, 0, PacketType.JoinRequest, SendOptions.None, (ushort)seq, (ushort)payloadSize); 621 | if (payloadSize > 0) 622 | { 623 | Buffer.BlockCopy(buffer, index, datagram, Const.FALCON_PACKET_HEADER_SIZE, payloadSize); 624 | } 625 | localPeer.RemovePeerOnNextUpdate(this); 626 | localPeer.EnqueuePacketToProcessOnNextUpdate(this.EndPoint, datagram); 627 | return false; 628 | } 629 | return Accept(); 630 | } 631 | case PacketType.DiscoverRequest: 632 | { 633 | DiscoverReply(); 634 | return true; 635 | } 636 | case PacketType.DiscoverReply: 637 | { 638 | // do nothing, DiscoveryReply only relevant when peer not added 639 | return true; 640 | } 641 | case PacketType.Bye: 642 | { 643 | localPeer.Log(LogLevel.Info, String.Format("Bye received from: {0}.", PeerName)); 644 | localPeer.RemovePeerOnNextUpdate(this); 645 | return false; 646 | } 647 | default: 648 | { 649 | localPeer.Log(LogLevel.Error, String.Format("Datagram dropped - unexpected type: {0}, received from authenticated peer: {1}.", type, PeerName)); 650 | return false; 651 | } 652 | } 653 | } 654 | 655 | // ASSUMPTION: Caller has checked UnreadPacketCount > 0, otherwise calling this would be 656 | // unneccessary. 657 | internal List Read() 658 | { 659 | allUnreadPackets.Clear(); 660 | 661 | allUnreadPackets.Capacity = UnreadPacketCount; 662 | 663 | if (noneReceiveChannel.Count > 0) 664 | allUnreadPackets.AddRange(noneReceiveChannel.Read()); 665 | if (inOrderReceiveChannel.Count > 0) 666 | allUnreadPackets.AddRange(inOrderReceiveChannel.Read()); 667 | if (reliableReceiveChannel.Count > 0) 668 | allUnreadPackets.AddRange(reliableReceiveChannel.Read()); 669 | if (reliableInOrderReceiveChannel.Count > 0) 670 | allUnreadPackets.AddRange(reliableInOrderReceiveChannel.Read()); 671 | 672 | unreadPacketCount = 0; 673 | 674 | return allUnreadPackets; 675 | } 676 | } 677 | } 678 | -------------------------------------------------------------------------------- /src/SendChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace FalconUDP 5 | { 6 | internal class SendChannel 7 | { 8 | private readonly Queue queue; 9 | private readonly SendOptions channelType; 10 | private readonly DatagramPool datagramPool; 11 | private Datagram currentDatagram; 12 | private int currentDatagramTotalBufferOffset; 13 | private ushort seqCount; 14 | 15 | internal bool IsReliable { get; private set; } 16 | internal bool HasDataToSend { get { return queue.Count > 0 || currentDatagramTotalBufferOffset > currentDatagram.Offset; } } 17 | 18 | public SendChannel(SendOptions channelType, DatagramPool sendDatagramPool) 19 | { 20 | this.channelType = channelType; 21 | this.queue = new Queue(); 22 | this.datagramPool = sendDatagramPool; 23 | this.IsReliable = (channelType & SendOptions.Reliable) == SendOptions.Reliable; 24 | 25 | GetNewDatagram(); 26 | } 27 | 28 | private void GetNewDatagram() 29 | { 30 | currentDatagram = datagramPool.Borrow(); 31 | currentDatagram.SendOptions = channelType; 32 | seqCount++; 33 | currentDatagram.Sequence = seqCount; 34 | currentDatagramTotalBufferOffset = currentDatagram.Offset; 35 | } 36 | 37 | private void EnqueueCurrentDatagram() 38 | { 39 | // Enqueue current datagram setting relevant fields. 40 | currentDatagram.Resize(currentDatagramTotalBufferOffset - currentDatagram.Offset); 41 | queue.Enqueue(currentDatagram); 42 | 43 | // get a new one 44 | GetNewDatagram(); 45 | } 46 | 47 | // used when datagram already constructed, e.g. re-sending unACKnowledged datagram 48 | internal void EnqueueSend(Datagram datagram) 49 | { 50 | queue.Enqueue(datagram); 51 | } 52 | 53 | internal void EnqueueSend(PacketType type, Packet packet) 54 | { 55 | // NOTE: packet may be null in the case of Falcon system messages. 56 | 57 | if (packet != null && packet.BytesWritten > FalconPeer.MaxPayloadSize) 58 | { 59 | throw new InvalidOperationException(String.Format("Packet size: {0}, greater than max: {1}", packet.BytesWritten, FalconPeer.MaxPayloadSize)); 60 | } 61 | 62 | bool isFalconHeaderWritten = currentDatagramTotalBufferOffset > currentDatagram.Offset; 63 | 64 | if (isFalconHeaderWritten) 65 | { 66 | if (packet != null && (packet.BytesWritten + Const.ADDITIONAL_PACKET_HEADER_SIZE) > (currentDatagram.Count - (currentDatagramTotalBufferOffset - currentDatagram.Offset))) // i.e. cannot fit 67 | { 68 | // enqueue the current args and get a new one 69 | EnqueueCurrentDatagram(); 70 | isFalconHeaderWritten = false; 71 | } 72 | } 73 | 74 | if (!isFalconHeaderWritten) 75 | { 76 | // write the falcon header 77 | FalconHelper.WriteFalconHeader(currentDatagram.BackingBuffer, 78 | currentDatagram.Offset, 79 | type, 80 | channelType, 81 | seqCount, 82 | packet == null ? (ushort)0 : (ushort)packet.BytesWritten); 83 | currentDatagramTotalBufferOffset += Const.FALCON_PACKET_HEADER_SIZE; 84 | } 85 | else 86 | { 87 | // TODO limit max additional to 100 so receive channel can distinguish ordinal seq 88 | 89 | // write additional header 90 | FalconHelper.WriteAdditionalFalconHeader(currentDatagram.BackingBuffer, 91 | currentDatagramTotalBufferOffset, 92 | type, 93 | channelType, 94 | packet == null ? (ushort)0 : (ushort)packet.BytesWritten); 95 | currentDatagramTotalBufferOffset += Const.ADDITIONAL_PACKET_HEADER_SIZE; 96 | } 97 | 98 | if (packet != null) 99 | { 100 | //--------------------------------------------------------------------------------------------------- 101 | packet.CopyBytes(0, currentDatagram.BackingBuffer, currentDatagramTotalBufferOffset, packet.BytesWritten); 102 | //--------------------------------------------------------------------------------------------------- 103 | 104 | currentDatagramTotalBufferOffset += packet.BytesWritten; 105 | } 106 | } 107 | 108 | // Get everything inc. current args if anything written to it 109 | internal Queue GetQueue() 110 | { 111 | if (currentDatagramTotalBufferOffset > currentDatagram.Offset) // i.e. something written 112 | { 113 | EnqueueCurrentDatagram(); 114 | } 115 | 116 | return queue; 117 | } 118 | 119 | // NOTE: This makes the channel unusuable. 120 | internal void ReturnLeasedDatagrams() 121 | { 122 | var queue = GetQueue(); 123 | while (queue.Count > 0) 124 | { 125 | datagramPool.Return(queue.Dequeue()); 126 | } 127 | datagramPool.Return(currentDatagram); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/SingleRandom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FalconUDP 4 | { 5 | internal static class SingleRandom 6 | { 7 | private static readonly Random random; 8 | 9 | static SingleRandom() 10 | { 11 | random = new Random(); 12 | } 13 | 14 | internal static int Next() 15 | { 16 | return random.Next(); 17 | } 18 | 19 | internal static int Next(int min, int max) 20 | { 21 | return random.Next(min, max); 22 | } 23 | 24 | internal static double NextDouble() 25 | { 26 | return random.NextDouble(); 27 | } 28 | 29 | internal static void NextBytes(byte[] buffer) 30 | { 31 | random.NextBytes(buffer); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SocketTransceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | 5 | namespace FalconUDP 6 | { 7 | internal class SocketTransceiver : IFalconTransceiver 8 | { 9 | private const int DefaultTypeOfService = 0; 10 | private const int EFTypeOfService = 184; 11 | 12 | private Socket socket; 13 | private IPEndPoint anyAddrEndPoint; 14 | private bool isEFSet; 15 | private FalconPeer localPeer; 16 | private EndPoint placeHolderEndPoint = new IPEndPoint(IPAddress.Any, 30000); 17 | 18 | 19 | public int BytesAvaliable 20 | { 21 | get 22 | { 23 | return socket.Available; 24 | } 25 | } 26 | 27 | public SocketTransceiver(FalconPeer localPeer) 28 | { 29 | this.localPeer = localPeer; 30 | this.anyAddrEndPoint = new IPEndPoint(IPAddress.Any, localPeer.Port); 31 | this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 32 | } 33 | 34 | public FalconOperationResult TryStart() 35 | { 36 | try 37 | { 38 | try 39 | { 40 | #if !(MONO || WINDOWS_UWP) 41 | socket.SetIPProtectionLevel(IPProtectionLevel.EdgeRestricted); 42 | #endif 43 | #if !LINUX 44 | socket.IOControl(-1744830452, new byte[] { 0 }, new byte[] { 0 }); // http://stackoverflow.com/questions/10332630/connection-reset-on-receiving-packet-in-udp-server 45 | #endif 46 | } 47 | catch 48 | { 49 | // we tried, but does fail for some and is not fatal e.g. http://steamcommunity.com/app/334560/discussions/0/405691147596493066/ 50 | } 51 | socket.Bind(anyAddrEndPoint); 52 | socket.Blocking = false; 53 | socket.ReceiveBufferSize = localPeer.ReceiveBufferSize; 54 | socket.SendBufferSize = localPeer.SendBufferSize; 55 | socket.EnableBroadcast = true; 56 | SetEF(); 57 | } 58 | catch (SocketException se) 59 | { 60 | // e.g. address already in use 61 | return new FalconOperationResult(se); 62 | } 63 | 64 | return FalconOperationResult.SuccessResult; 65 | } 66 | 67 | private void SetEF() 68 | { 69 | socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, EFTypeOfService); 70 | isEFSet = true; 71 | } 72 | 73 | private void UnsetEF() 74 | { 75 | socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, DefaultTypeOfService); 76 | isEFSet = false; 77 | } 78 | 79 | public void Stop() 80 | { 81 | try 82 | { 83 | #if WINDOWS_UWP 84 | socket.Dispose(); 85 | #else 86 | socket.Close(); 87 | #endif 88 | } 89 | catch { } 90 | } 91 | 92 | // returns 0 if fatal failure receiving from epFrom 93 | public int Read(byte[] receiveBuffer, ref IPEndPoint ipFrom) 94 | { 95 | int size = 0; 96 | try 97 | { 98 | size = socket.ReceiveFrom(receiveBuffer, ref placeHolderEndPoint); 99 | ipFrom = (IPEndPoint)placeHolderEndPoint; 100 | } 101 | catch (SocketException se) 102 | { 103 | localPeer.Log(LogLevel.Error, String.Format("Socket Exception {0} {1}, while receiving from {2}." 104 | #if WINDOWS_UWP 105 | , se.SocketErrorCode 106 | #else 107 | , se.ErrorCode 108 | #endif 109 | , se.Message 110 | , ipFrom)); 111 | } 112 | return size; 113 | } 114 | 115 | // return false if fatal failure sending to ip 116 | public bool Send(byte[] buffer, int index, int count, IPEndPoint ip, bool expidite) 117 | { 118 | if (expidite != isEFSet) 119 | { 120 | if (expidite) 121 | SetEF(); 122 | else 123 | UnsetEF(); 124 | } 125 | 126 | try 127 | { 128 | socket.SendTo(buffer, index, count, SocketFlags.None, ip); 129 | } 130 | catch (SocketException se) 131 | { 132 | localPeer.Log(LogLevel.Error, String.Format("Socket Error {0}: {1}, sending to peer: {2}" 133 | #if WINDOWS_UWP 134 | , se.SocketErrorCode 135 | #else 136 | , se.ErrorCode 137 | #endif 138 | , se.Message 139 | , ip)); 140 | return false; 141 | } 142 | 143 | return true; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Statistics.cs: -------------------------------------------------------------------------------- 1 | namespace FalconUDP 2 | { 3 | /// 4 | /// Provides total number of bytes sent and recieved in the last second. 5 | /// 6 | /// Statistics are only collected when is called. 7 | public class Statistics 8 | { 9 | private float ellapsedSecondsSinceSecondStart; 10 | private int bytesSentInLastSecond; 11 | private int bytesReceivedInLastSecond; 12 | private int bytesSentPerSecond; 13 | private int bytesReceivedPerSecond; 14 | 15 | /// 16 | /// Total number of bytes sent in the last second (including IP, UDP and FalconUDP header bytes). 17 | /// 18 | public int BytesSentPerSecond 19 | { 20 | get { return bytesSentPerSecond; } 21 | } 22 | 23 | /// 24 | /// Total number of bytes received in the last second (including IP, UDP and FalconUDP header bytes). 25 | /// 26 | public int BytesReceivedPerSecond 27 | { 28 | get { return bytesReceivedPerSecond; } 29 | } 30 | 31 | internal Statistics() 32 | { 33 | } 34 | 35 | internal void Reset() 36 | { 37 | ellapsedSecondsSinceSecondStart = 0; 38 | bytesSentInLastSecond = 0; 39 | bytesReceivedInLastSecond = 0; 40 | bytesSentPerSecond = 0; 41 | bytesReceivedPerSecond = 0; 42 | } 43 | 44 | internal void AddBytesSent(int falconPacketSize) 45 | { 46 | bytesSentInLastSecond += (falconPacketSize + Const.CARRIER_PROTOCOL_HEADER_SIZE); 47 | } 48 | 49 | internal void AddBytesReceived(int falconPacketSize) 50 | { 51 | bytesReceivedInLastSecond += (falconPacketSize + Const.CARRIER_PROTOCOL_HEADER_SIZE); 52 | } 53 | 54 | internal void Update(float dt) 55 | { 56 | ellapsedSecondsSinceSecondStart += dt; 57 | if (ellapsedSecondsSinceSecondStart >= 1.0f) // NOTE: error margin of one update 58 | { 59 | bytesReceivedPerSecond = bytesReceivedInLastSecond; 60 | bytesSentPerSecond = bytesSentInLastSecond; 61 | bytesReceivedInLastSecond = 0; 62 | bytesSentInLastSecond = 0; 63 | ellapsedSecondsSinceSecondStart = 0; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/TransceiverFactory.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace FalconUDP 3 | { 4 | internal static class TransceiverFactory 5 | { 6 | 7 | internal static IFalconTransceiver Create(FalconPeer localPeer) 8 | { 9 | #if NETFX_CORE && !WINDOWS_UWP 10 | return new DatagramSocketTransceiver(localPeer); 11 | #elif PS4 12 | return new AutonomousTransciever(localPeer); 13 | #else 14 | return new SocketTransceiver(localPeer); 15 | #endif 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/UPnPInternetGatewayDevice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Xml; 7 | 8 | namespace FalconUDP 9 | { 10 | internal class UPnPInternetGatewayDevice 11 | { 12 | private string serviceName; 13 | private string controlUrl; 14 | 15 | public UPnPInternetGatewayDevice(string serviceName, string absoluteControlUri) 16 | { 17 | this.serviceName = serviceName; 18 | this.controlUrl = absoluteControlUri; 19 | } 20 | 21 | private static string GetUPnPProtocolString(ProtocolType type) 22 | { 23 | if (type == ProtocolType.Tcp) 24 | return "TCP"; 25 | if (type == ProtocolType.Udp) 26 | return "UDP"; 27 | throw new ArgumentException(type.ToString()); 28 | } 29 | 30 | public static void BeginCreate(string locationUri, Action callback) 31 | { 32 | #if WINDOWS_UWP 33 | return; 34 | #else 35 | 36 | string controlUri = null; 37 | string serviceName = null; 38 | 39 | Task.Factory.StartNew(() => 40 | { 41 | var request = HttpWebRequest.Create(locationUri); 42 | 43 | using (var webResponse = (HttpWebResponse)request.GetResponse()) 44 | { 45 | using (var stream = webResponse.GetResponseStream()) 46 | { 47 | XmlDocument doc = new XmlDocument(); 48 | doc.Load(stream); 49 | XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable); 50 | namespaceManager.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); 51 | 52 | // we are looking for a InternetGatewayDevice 53 | XmlNode deviceTypeNode = doc.SelectSingleNode("//tns:device/tns:deviceType/text()", namespaceManager); 54 | if (!deviceTypeNode.Value.Contains("InternetGatewayDevice")) 55 | return; 56 | 57 | // get the controlURL of WANIPConnection or WANPPPConnection 58 | XmlNode controlUrlElement = doc.SelectSingleNode("//tns:service[tns:serviceType='urn:schemas-upnp-org:service:WANIPConnection:1']/tns:controlURL/text()", namespaceManager); 59 | serviceName = "WANIPConnection:1"; 60 | if (controlUrlElement == null) 61 | { 62 | controlUrlElement = doc.SelectSingleNode("//tns:service[tns:serviceType='urn:schemas-upnp-org:service:WANPPPConnection:1']/tns:controlURL/text()", namespaceManager); 63 | serviceName = "WANPPPConnection:1"; 64 | if (controlUrlElement == null) 65 | { 66 | return; 67 | } 68 | } 69 | 70 | string url = controlUrlElement.Value; 71 | if (url.StartsWith("http")) // i.e. already absolute 72 | { 73 | controlUri = url; 74 | } 75 | else 76 | { 77 | string baseUri = new Uri(locationUri).GetLeftPart(UriPartial.Authority); 78 | controlUri = baseUri + (url.StartsWith("/") ? url : "/" + url); 79 | } 80 | } 81 | } 82 | 83 | }).ContinueWith(completedTask => 84 | { 85 | if (completedTask.Exception != null || controlUri == null) // IMPORTANT Exception must be accessed otherwise thrown on finalize 86 | { 87 | callback(null); 88 | } 89 | else 90 | { 91 | UPnPInternetGatewayDevice device = new UPnPInternetGatewayDevice(serviceName, controlUri); 92 | callback(device); 93 | } 94 | }); 95 | #endif 96 | } 97 | 98 | private bool SendCommand(string requestBody, string function) 99 | { 100 | #if WINDOWS_UWP 101 | return false; 102 | #else 103 | 104 | try 105 | { 106 | string request = "" + 107 | "" + 108 | "" + 109 | requestBody + 110 | "" + 111 | ""; 112 | 113 | byte[] body = Encoding.UTF8.GetBytes(request); 114 | 115 | HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(controlUrl); 116 | webRequest.Method = "POST"; 117 | webRequest.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:" + serviceName + "#" + function + "\""); 118 | webRequest.ContentType = "text/xml; charset=\"utf-8\""; 119 | 120 | using(var requestStream = webRequest.GetRequestStream()) 121 | { 122 | requestStream.Write(body, 0, body.Length); 123 | } 124 | 125 | using (var webResponse = (HttpWebResponse)webRequest.GetResponse()) 126 | { 127 | return webResponse.StatusCode == HttpStatusCode.OK; 128 | // NOTE: Failure can be for a number or reasons, one I have encountered is 129 | // is the mapping was already added. 130 | } 131 | } 132 | catch 133 | { 134 | // NOTE: GetResponse() throws exception for error responses 135 | return false; 136 | } 137 | #endif 138 | } 139 | 140 | public bool TryAddForwardingRule(ProtocolType protocol, IPAddress addr, ushort port, string description) 141 | { 142 | #if WINDOWS_UWP 143 | return false; 144 | #else 145 | string protocolString = GetUPnPProtocolString(protocol); 146 | string portString = port.ToString(); 147 | string request = String.Format("", serviceName) + 148 | String.Format(@" 149 | {1} 150 | {2} 151 | {3} 152 | {4} 153 | 1 154 | {5} 155 | 0 156 | ", serviceName, portString, protocolString, portString, addr, description); 157 | 158 | return SendCommand(request, "AddPortMapping"); 159 | #endif 160 | } 161 | 162 | public bool TryDeleteForwardingRule(ProtocolType protocol, ushort port) 163 | { 164 | #if WINDOWS_UWP 165 | return false; 166 | #else 167 | string protocolString = GetUPnPProtocolString(protocol); 168 | string portString = port.ToString(); 169 | string request = String.Format("", serviceName) + 170 | String.Format(@" 171 | {1} 172 | {2} 173 | ", serviceName, portString, protocolString); 174 | 175 | return SendCommand(request, "DeletePortMapping"); 176 | #endif 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/UWPExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FalconUDP 9 | { 10 | public static class UWPNetExtensions 11 | { 12 | public static long GetAddressAsLong(this IPAddress ip) 13 | { 14 | if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) 15 | throw new InvalidOperationException(ip.AddressFamily.ToString()); 16 | 17 | var value = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); 18 | return (long)value; 19 | } 20 | } 21 | 22 | } 23 | --------------------------------------------------------------------------------