ayende / rhino-licensing

A software licensing framework
http://ayende.com
BSD 3-Clause "New" or "Revised" License
333 stars 167 forks source link

SntpClient failure notification #12

Open trailway opened 9 years ago

trailway commented 9 years ago

The SntpClient class is notifying failures multiple times. This can be reproduced by pulling the network cable from machine. I added the following check to avoid this:

    public void BeginGetDate(Action<DateTime> getTime, Action failure)
    {
        _index += 1;
        if (_hosts.Length <= _index)
        {
            if (_hosts.Length == _index)
            {
                failure();
            }
            return;
        }

I suspect that there may be a deeper problem here but the fix isn't obvious to me.

ayende commented 9 years ago

The actual reason for the failure isn't that interesting. It can be a network cable not connected, bad WIFI connection,etc.

We want to handle all errors in the same manner

trailway commented 9 years ago

I think that the problem is that MaybeOperationTimeout is not checking to see if a socket was created:

    private void MaybeOperationTimeout(object state, bool timedOut)
    {
        if (timedOut == false)
            return;

        var theState = (State)state;
        try
        {
            theState.Socket?.Close();
        }

This results in an unnecessary retry because Dns.BeginGetHostAddresses is failing (it takes longer than 1/2 second) and there is no socket.

trailway commented 9 years ago

The problem is that it is notifying the failure multiple times. I think that it should either call the success or failure function once.

I think that the root cause of the problem that MaybeOperationTimeout is called from RegisterWaitForTimeout which is called from BeginGetDate and there is a timeout but no socket. Dns.BeginGetHostAddresses throws an exception (it takes a while) which causes a recursive BeginGetDate. The null socket also causes a BeginGetDate as well.

What I did was: 1) Only notify 1 time 2) Test if the socket is null in MaybeOperationTimeout

Thanks for contributing the code.

On Thu, Aug 27, 2015 at 12:18 AM, Ayende Rahien notifications@github.com wrote:

The actual reason for the failure isn't that interesting. It can be a network cable not connected, bad WIFI connection,etc.

We want to handle all errors in the same manner

— Reply to this email directly or view it on GitHub https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135295215 .

ayende commented 9 years ago

Can you show your changes? I'm not sure that I can follow from the description.

trailway commented 9 years ago

See attached code.

On Fri, Aug 28, 2015 at 5:53 AM, Ayende Rahien notifications@github.com wrote:

Can you show your changes? I'm not sure that I can follow from the description.

— Reply to this email directly or view it on GitHub https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135738405 .

using System; using System.CodeDom.Compiler; using System.Net; using System.Net.Sockets; using System.Threading;

namespace LicensingShared { ///

/// Provides support for asynchronously acquiring the current time
/// via well known sources for time.
/// </summary>

/// <remarks>
/// This code is taken from <a href="https://github.com/ayende/rhino-licensing">rhino-licensing</a>.
/// See <a href="https://github.com/ayende/rhino-licensing/blob/master/license.txt">copyright notice</a>.
/// Minor corrections where made to the code as a result of testing.
/// </remarks>
[GeneratedCode(@"rhino-licensing", @"08/25/2015")]
internal class SntpClient
{
    private const byte SntpDataLength = 48;
    private readonly string[] _hosts;
    private int _index = -1;
    public SntpClient(string[] hosts)
    {
        _hosts = hosts;
    }

    private static bool GetIsServerMode(byte[] sntpData)
    {
        return (sntpData[0] & 0x7) == 4 /* server mode */;
    }

    private static DateTime GetTransmitTimestamp(byte[] sntpData)
    {
        var milliSeconds = GetMilliSeconds(sntpData, 40);
        return ComputeDate(milliSeconds);
    }

    private static DateTime ComputeDate(ulong milliseconds)
    {
        return new DateTime(1900, 1, 1).Add(TimeSpan.FromMilliseconds(milliseconds));
    }

    private static ulong GetMilliSeconds(byte[] sntpData, byte offset)
    {
        ulong intpart = 0, fractpart = 0;

        for (var i = 0; i <= 3; i++)
        {
            intpart = 256 * intpart + sntpData[offset + i];
        }
        for (var i = 4; i <= 7; i++)
        {
            fractpart = 256 * fractpart + sntpData[offset + i];
        }
        var milliseconds = intpart * 1000 + (fractpart * 1000) / 0x100000000L;
        return milliseconds;
    }

    public void BeginGetDate(Action<DateTime> getTime, Action failure)
    {
        _index += 1;
        if (_hosts.Length <= _index)
        {
            if (_hosts.Length == _index)
            {
                failure();
            }
            return;
        }
        try
        {
            var host = _hosts[_index];
            var state = new State(null, null, getTime, failure);
            var result = Dns.BeginGetHostAddresses(host, EndGetHostAddress, state);
            RegisterWaitForTimeout(state, result);
        }
        catch (Exception)
        {
            // retry, recursion stops at the end of the hosts
            BeginGetDate(getTime, failure);
        }
    }

    private void EndGetHostAddress(IAsyncResult asyncResult)
    {
        var state = (State)asyncResult.AsyncState;
        try
        {
            var addresses = Dns.EndGetHostAddresses(asyncResult);
            var endPoint = new IPEndPoint(addresses[0], 123);

            var socket = new UdpClient();
            socket.Connect(endPoint);
            socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 500);
            socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 500);
            var sntpData = new byte[SntpDataLength];
            sntpData[0] = 0x1B; // version = 4 & mode = 3 (client)

            var newState = new State(socket, endPoint, state.GetTime, state.Failure);
            var result = socket.BeginSend(sntpData, sntpData.Length, EndSend, newState);
            RegisterWaitForTimeout(newState, result);
        }
        catch (Exception)
        {
            // retry, recursion stops at the end of the hosts
            BeginGetDate(state.GetTime, state.Failure);
        }
    }

    private void RegisterWaitForTimeout(State newState, IAsyncResult result)
    {
        if (result != null)
        {
            ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, MaybeOperationTimeout, newState, 500,
                true);
        }
    }

    private void MaybeOperationTimeout(object state, bool timedOut)
    {
        if (timedOut == false)
            return;

        var theState = (State)state;
        try
        {
            theState.Socket?.Close();
        }
        catch (Exception)
        {
            // retry, recursion stops at the end of the hosts
            BeginGetDate(theState.GetTime, theState.Failure);
        }
    }

    private void EndSend(IAsyncResult ar)
    {
        var state = (State)ar.AsyncState;
        try
        {
            state.Socket.EndSend(ar);
            var result = state.Socket.BeginReceive(EndReceive, state);
            RegisterWaitForTimeout(state, result);
        }
        catch
        {
            state.Socket.Close();
            BeginGetDate(state.GetTime, state.Failure);
        }
    }

    private void EndReceive(IAsyncResult ar)
    {
        var state = (State)ar.AsyncState;
        try
        {
            var endPoint = state.EndPoint;
            var sntpData = state.Socket.EndReceive(ar, ref endPoint);
            if (IsResponseValid(sntpData) == false)
            {
                state.Failure();
                return;
            }
            var transmitTimestamp = GetTransmitTimestamp(sntpData);
            state.GetTime(transmitTimestamp);
        }
        catch
        {
            BeginGetDate(state.GetTime, state.Failure);
        }
        finally
        {
            state.Socket.Close();
        }
    }

    private bool IsResponseValid(byte[] sntpData)
    {
        return sntpData.Length >= SntpDataLength && GetIsServerMode(sntpData);
    }

    #region Nested type: State

    private class State
    {
        public State(UdpClient socket, IPEndPoint endPoint, Action<DateTime> getTime, Action failure)
        {
            Socket = socket;
            EndPoint = endPoint;
            GetTime = getTime;
            Failure = failure;
        }

        public UdpClient Socket { get; private set; }

        public Action<DateTime> GetTime { get; private set; }

        public Action Failure { get; private set; }

        public IPEndPoint EndPoint { get; private set; }
    }

    #endregion
}

}

ayende commented 9 years ago

I can't really read this. Can you send this as a PR, that way it would be much easier to see the changes.

Hibernating Rhinos Ltd

Oren Eini* l CEO l *Mobile: + 972-52-548-6969

Office: +972-4-622-7811 l Fax: +972-153-4-622-7811

On Fri, Aug 28, 2015 at 4:54 PM, trailway notifications@github.com wrote:

See attached code.

On Fri, Aug 28, 2015 at 5:53 AM, Ayende Rahien notifications@github.com wrote:

Can you show your changes? I'm not sure that I can follow from the description.

— Reply to this email directly or view it on GitHub < https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135738405

.

using System; using System.CodeDom.Compiler; using System.Net; using System.Net.Sockets; using System.Threading;

namespace LicensingShared { ///

/// Provides support for asynchronously acquiring the current time /// via well known sources for time. ///

/// /// This code is taken from rhino-licensing. /// See copyright notice. /// Minor corrections where made to the code as a result of testing. /// [GeneratedCode(@"rhino-licensing", @"08/25/2015")] internal class SntpClient { private const byte SntpDataLength = 48; private readonly string[] _hosts; private int _index = -1;

public SntpClient(string[] hosts) { _hosts = hosts; }

private static bool GetIsServerMode(byte[] sntpData) { return (sntpData[0] & 0x7) == 4 /* server mode */; }

private static DateTime GetTransmitTimestamp(byte[] sntpData) { var milliSeconds = GetMilliSeconds(sntpData, 40); return ComputeDate(milliSeconds); }

private static DateTime ComputeDate(ulong milliseconds) { return new DateTime(1900, 1, 1).Add(TimeSpan.FromMilliseconds(milliseconds)); }

private static ulong GetMilliSeconds(byte[] sntpData, byte offset) { ulong intpart = 0, fractpart = 0;

for (var i = 0; i <= 3; i++) { intpart = 256 * intpart + sntpData[offset + i]; } for (var i = 4; i <= 7; i++) { fractpart = 256 * fractpart + sntpData[offset + i]; } var milliseconds = intpart * 1000 + (fractpart * 1000) / 0x100000000L; return milliseconds; }

public void BeginGetDate(Action getTime, Action failure) { _index += 1; if (_hosts.Length <= _index) { if (_hosts.Length == _index) { failure(); } return; } try { var host = _hosts[_index]; var state = new State(null, null, getTime, failure); var result = Dns.BeginGetHostAddresses(host, EndGetHostAddress, state); RegisterWaitForTimeout(state, result); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(getTime, failure); } }

private void EndGetHostAddress(IAsyncResult asyncResult) { var state = (State)asyncResult.AsyncState; try { var addresses = Dns.EndGetHostAddresses(asyncResult); var endPoint = new IPEndPoint(addresses[0], 123);

var socket = new UdpClient(); socket.Connect(endPoint); socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 500); socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 500); var sntpData = new byte[SntpDataLength]; sntpData[0] = 0x1B; // version = 4 & mode = 3 (client)

var newState = new State(socket, endPoint, state.GetTime, state.Failure); var result = socket.BeginSend(sntpData, sntpData.Length, EndSend, newState); RegisterWaitForTimeout(newState, result); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(state.GetTime, state.Failure); } }

private void RegisterWaitForTimeout(State newState, IAsyncResult result) { if (result != null) { ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, MaybeOperationTimeout, newState, 500, true); } }

private void MaybeOperationTimeout(object state, bool timedOut) { if (timedOut == false) return;

var theState = (State)state; try { theState.Socket?.Close(); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(theState.GetTime, theState.Failure); } }

private void EndSend(IAsyncResult ar) { var state = (State)ar.AsyncState; try { state.Socket.EndSend(ar); var result = state.Socket.BeginReceive(EndReceive, state); RegisterWaitForTimeout(state, result); } catch { state.Socket.Close(); BeginGetDate(state.GetTime, state.Failure); } }

private void EndReceive(IAsyncResult ar) { var state = (State)ar.AsyncState; try { var endPoint = state.EndPoint; var sntpData = state.Socket.EndReceive(ar, ref endPoint); if (IsResponseValid(sntpData) == false) { state.Failure(); return; } var transmitTimestamp = GetTransmitTimestamp(sntpData); state.GetTime(transmitTimestamp); } catch { BeginGetDate(state.GetTime, state.Failure); } finally { state.Socket.Close(); } }

private bool IsResponseValid(byte[] sntpData) { return sntpData.Length >= SntpDataLength && GetIsServerMode(sntpData); }

region Nested type: State

private class State { public State(UdpClient socket, IPEndPoint endPoint, Action getTime, Action failure) { Socket = socket; EndPoint = endPoint; GetTime = getTime; Failure = failure; }

public UdpClient Socket { get; private set; }

public Action GetTime { get; private set; }

public Action Failure { get; private set; }

public IPEndPoint EndPoint { get; private set; } }

endregion

} }

— Reply to this email directly or view it on GitHub https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135780507 .

trailway commented 9 years ago

Done.

My first pull request.

On Fri, Aug 28, 2015 at 11:07 AM, Ayende Rahien notifications@github.com wrote:

I can't really read this. Can you send this as a PR, that way it would be much easier to see the changes.

Hibernating Rhinos Ltd

Oren Eini* l CEO l *Mobile: + 972-52-548-6969

Office: +972-4-622-7811 l Fax: +972-153-4-622-7811

On Fri, Aug 28, 2015 at 4:54 PM, trailway notifications@github.com wrote:

See attached code.

On Fri, Aug 28, 2015 at 5:53 AM, Ayende Rahien <notifications@github.com

wrote:

Can you show your changes? I'm not sure that I can follow from the description.

— Reply to this email directly or view it on GitHub <

https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135738405

.

using System; using System.CodeDom.Compiler; using System.Net; using System.Net.Sockets; using System.Threading;

namespace LicensingShared { ///

/// Provides support for asynchronously acquiring the current time /// via well known sources for time. ///

/// /// This code is taken from rhino-licensing. /// See copyright notice. /// Minor corrections where made to the code as a result of testing. /// [GeneratedCode(@"rhino-licensing", @"08/25/2015")] internal class SntpClient { private const byte SntpDataLength = 48; private readonly string[] _hosts; private int _index = -1;

public SntpClient(string[] hosts) { _hosts = hosts; }

private static bool GetIsServerMode(byte[] sntpData) { return (sntpData[0] & 0x7) == 4 /* server mode */; }

private static DateTime GetTransmitTimestamp(byte[] sntpData) { var milliSeconds = GetMilliSeconds(sntpData, 40); return ComputeDate(milliSeconds); }

private static DateTime ComputeDate(ulong milliseconds) { return new DateTime(1900, 1, 1).Add(TimeSpan.FromMilliseconds(milliseconds)); }

private static ulong GetMilliSeconds(byte[] sntpData, byte offset) { ulong intpart = 0, fractpart = 0;

for (var i = 0; i <= 3; i++) { intpart = 256 * intpart + sntpData[offset + i]; } for (var i = 4; i <= 7; i++) { fractpart = 256 * fractpart + sntpData[offset + i]; } var milliseconds = intpart * 1000 + (fractpart * 1000) / 0x100000000L; return milliseconds; }

public void BeginGetDate(Action getTime, Action failure) { _index += 1; if (_hosts.Length <= _index) { if (_hosts.Length == _index) { failure(); } return; } try { var host = _hosts[_index]; var state = new State(null, null, getTime, failure); var result = Dns.BeginGetHostAddresses(host, EndGetHostAddress, state); RegisterWaitForTimeout(state, result); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(getTime, failure); } }

private void EndGetHostAddress(IAsyncResult asyncResult) { var state = (State)asyncResult.AsyncState; try { var addresses = Dns.EndGetHostAddresses(asyncResult); var endPoint = new IPEndPoint(addresses[0], 123);

var socket = new UdpClient(); socket.Connect(endPoint); socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 500); socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 500); var sntpData = new byte[SntpDataLength]; sntpData[0] = 0x1B; // version = 4 & mode = 3 (client)

var newState = new State(socket, endPoint, state.GetTime, state.Failure); var result = socket.BeginSend(sntpData, sntpData.Length, EndSend, newState); RegisterWaitForTimeout(newState, result); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(state.GetTime, state.Failure); } }

private void RegisterWaitForTimeout(State newState, IAsyncResult result) { if (result != null) { ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, MaybeOperationTimeout, newState, 500, true); } }

private void MaybeOperationTimeout(object state, bool timedOut) { if (timedOut == false) return;

var theState = (State)state; try { theState.Socket?.Close(); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(theState.GetTime, theState.Failure); } }

private void EndSend(IAsyncResult ar) { var state = (State)ar.AsyncState; try { state.Socket.EndSend(ar); var result = state.Socket.BeginReceive(EndReceive, state); RegisterWaitForTimeout(state, result); } catch { state.Socket.Close(); BeginGetDate(state.GetTime, state.Failure); } }

private void EndReceive(IAsyncResult ar) { var state = (State)ar.AsyncState; try { var endPoint = state.EndPoint; var sntpData = state.Socket.EndReceive(ar, ref endPoint); if (IsResponseValid(sntpData) == false) { state.Failure(); return; } var transmitTimestamp = GetTransmitTimestamp(sntpData); state.GetTime(transmitTimestamp); } catch { BeginGetDate(state.GetTime, state.Failure); } finally { state.Socket.Close(); } }

private bool IsResponseValid(byte[] sntpData) { return sntpData.Length >= SntpDataLength && GetIsServerMode(sntpData); }

region Nested type: State

private class State { public State(UdpClient socket, IPEndPoint endPoint, Action getTime, Action failure) { Socket = socket; EndPoint = endPoint; GetTime = getTime; Failure = failure; }

public UdpClient Socket { get; private set; }

public Action GetTime { get; private set; }

public Action Failure { get; private set; }

public IPEndPoint EndPoint { get; private set; } }

endregion

} }

— Reply to this email directly or view it on GitHub < https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135780507

.

— Reply to this email directly or view it on GitHub https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135817838 .

ayende commented 9 years ago

Thanks, I mreged it.

Hibernating Rhinos Ltd

Oren Eini* l CEO l *Mobile: + 972-52-548-6969

Office: +972-4-622-7811 l Fax: +972-153-4-622-7811

On Fri, Aug 28, 2015 at 8:46 PM, trailway notifications@github.com wrote:

Done.

My first pull request.

On Fri, Aug 28, 2015 at 11:07 AM, Ayende Rahien notifications@github.com wrote:

I can't really read this. Can you send this as a PR, that way it would be much easier to see the changes.

Hibernating Rhinos Ltd

Oren Eini* l CEO l *Mobile: + 972-52-548-6969

Office: +972-4-622-7811 l Fax: +972-153-4-622-7811

On Fri, Aug 28, 2015 at 4:54 PM, trailway notifications@github.com wrote:

See attached code.

On Fri, Aug 28, 2015 at 5:53 AM, Ayende Rahien < notifications@github.com

wrote:

Can you show your changes? I'm not sure that I can follow from the description.

— Reply to this email directly or view it on GitHub <

https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135738405

.

using System; using System.CodeDom.Compiler; using System.Net; using System.Net.Sockets; using System.Threading;

namespace LicensingShared { ///

/// Provides support for asynchronously acquiring the current time /// via well known sources for time. ///

/// /// This code is taken from rhino-licensing. /// See copyright notice. /// Minor corrections where made to the code as a result of testing. /// [GeneratedCode(@"rhino-licensing", @"08/25/2015")] internal class SntpClient { private const byte SntpDataLength = 48; private readonly string[] _hosts; private int _index = -1;

public SntpClient(string[] hosts) { _hosts = hosts; }

private static bool GetIsServerMode(byte[] sntpData) { return (sntpData[0] & 0x7) == 4 /* server mode */; }

private static DateTime GetTransmitTimestamp(byte[] sntpData) { var milliSeconds = GetMilliSeconds(sntpData, 40); return ComputeDate(milliSeconds); }

private static DateTime ComputeDate(ulong milliseconds) { return new DateTime(1900, 1, 1).Add(TimeSpan.FromMilliseconds(milliseconds)); }

private static ulong GetMilliSeconds(byte[] sntpData, byte offset) { ulong intpart = 0, fractpart = 0;

for (var i = 0; i <= 3; i++) { intpart = 256 * intpart + sntpData[offset + i]; } for (var i = 4; i <= 7; i++) { fractpart = 256 * fractpart + sntpData[offset + i]; } var milliseconds = intpart * 1000 + (fractpart * 1000) / 0x100000000L; return milliseconds; }

public void BeginGetDate(Action getTime, Action failure) { _index += 1; if (_hosts.Length <= _index) { if (_hosts.Length == _index) { failure(); } return; } try { var host = _hosts[_index]; var state = new State(null, null, getTime, failure); var result = Dns.BeginGetHostAddresses(host, EndGetHostAddress, state); RegisterWaitForTimeout(state, result); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(getTime, failure); } }

private void EndGetHostAddress(IAsyncResult asyncResult) { var state = (State)asyncResult.AsyncState; try { var addresses = Dns.EndGetHostAddresses(asyncResult); var endPoint = new IPEndPoint(addresses[0], 123);

var socket = new UdpClient(); socket.Connect(endPoint); socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 500); socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 500); var sntpData = new byte[SntpDataLength]; sntpData[0] = 0x1B; // version = 4 & mode = 3 (client)

var newState = new State(socket, endPoint, state.GetTime, state.Failure); var result = socket.BeginSend(sntpData, sntpData.Length, EndSend, newState); RegisterWaitForTimeout(newState, result); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(state.GetTime, state.Failure); } }

private void RegisterWaitForTimeout(State newState, IAsyncResult result) { if (result != null) { ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, MaybeOperationTimeout, newState, 500, true); } }

private void MaybeOperationTimeout(object state, bool timedOut) { if (timedOut == false) return;

var theState = (State)state; try { theState.Socket?.Close(); } catch (Exception) { // retry, recursion stops at the end of the hosts BeginGetDate(theState.GetTime, theState.Failure); } }

private void EndSend(IAsyncResult ar) { var state = (State)ar.AsyncState; try { state.Socket.EndSend(ar); var result = state.Socket.BeginReceive(EndReceive, state); RegisterWaitForTimeout(state, result); } catch { state.Socket.Close(); BeginGetDate(state.GetTime, state.Failure); } }

private void EndReceive(IAsyncResult ar) { var state = (State)ar.AsyncState; try { var endPoint = state.EndPoint; var sntpData = state.Socket.EndReceive(ar, ref endPoint); if (IsResponseValid(sntpData) == false) { state.Failure(); return; } var transmitTimestamp = GetTransmitTimestamp(sntpData); state.GetTime(transmitTimestamp); } catch { BeginGetDate(state.GetTime, state.Failure); } finally { state.Socket.Close(); } }

private bool IsResponseValid(byte[] sntpData) { return sntpData.Length >= SntpDataLength && GetIsServerMode(sntpData); }

region Nested type: State

private class State { public State(UdpClient socket, IPEndPoint endPoint, Action getTime, Action failure) { Socket = socket; EndPoint = endPoint; GetTime = getTime; Failure = failure; }

public UdpClient Socket { get; private set; }

public Action GetTime { get; private set; }

public Action Failure { get; private set; }

public IPEndPoint EndPoint { get; private set; } }

endregion

} }

— Reply to this email directly or view it on GitHub <

https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135780507

.

— Reply to this email directly or view it on GitHub < https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135817838

.

— Reply to this email directly or view it on GitHub https://github.com/ayende/rhino-licensing/issues/12#issuecomment-135842901 .