commit fbcd550a2a1f31a9ca3b8a27af8f1a2be9b46d72 Author: lidgren Date: Thu May 6 18:30:27 2010 +0000 transferred from trunk/Generation3 of lidgren-network diff --git a/AllSamples.sln b/AllSamples.sln new file mode 100644 index 0000000..f132bd3 --- /dev/null +++ b/AllSamples.sln @@ -0,0 +1,79 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lidgren.Network", "Lidgren.Network\Lidgren.Network.csproj", "{FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{9D7AC4F7-39CD-4BC8-8F45-00B67C196340}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{DA6697E7-4DD4-45EF-90A7-2FC265855019}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamplesCommon", "Samples\SamplesCommon\SamplesCommon.csproj", "{773069DA-B66E-4667-ADCB-0D215AD8CF3E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageServer", "Samples\ImageServer\ImageServer.csproj", "{36382EFB-BE9E-45B3-BEC8-E70F65CDF868}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageClient", "Samples\ImageClient\ImageClient.csproj", "{69E64B8C-4736-4334-87BF-DD631A3AD144}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatServer", "Samples\ChatServer\ChatServer.csproj", "{E2711561-B3C9-4580-B054-891CE54E15EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatClient", "Samples\ChatClient\ChatClient.csproj", "{321F68AE-7F97-415E-A3F9-7C477EFF95EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableServer", "Samples\DurableServer\DurableServer.csproj", "{034984CA-FB37-44AF-BBF9-EC58ED75F5F3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableClient", "Samples\DurableClient\DurableClient.csproj", "{0B4B02BB-0F43-4466-A369-0682281AF60E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Release|Any CPU.Build.0 = Release|Any CPU + {9D7AC4F7-39CD-4BC8-8F45-00B67C196340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D7AC4F7-39CD-4BC8-8F45-00B67C196340}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D7AC4F7-39CD-4BC8-8F45-00B67C196340}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D7AC4F7-39CD-4BC8-8F45-00B67C196340}.Release|Any CPU.Build.0 = Release|Any CPU + {773069DA-B66E-4667-ADCB-0D215AD8CF3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {773069DA-B66E-4667-ADCB-0D215AD8CF3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {773069DA-B66E-4667-ADCB-0D215AD8CF3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {773069DA-B66E-4667-ADCB-0D215AD8CF3E}.Release|Any CPU.Build.0 = Release|Any CPU + {36382EFB-BE9E-45B3-BEC8-E70F65CDF868}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36382EFB-BE9E-45B3-BEC8-E70F65CDF868}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36382EFB-BE9E-45B3-BEC8-E70F65CDF868}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36382EFB-BE9E-45B3-BEC8-E70F65CDF868}.Release|Any CPU.Build.0 = Release|Any CPU + {69E64B8C-4736-4334-87BF-DD631A3AD144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69E64B8C-4736-4334-87BF-DD631A3AD144}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69E64B8C-4736-4334-87BF-DD631A3AD144}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69E64B8C-4736-4334-87BF-DD631A3AD144}.Release|Any CPU.Build.0 = Release|Any CPU + {E2711561-B3C9-4580-B054-891CE54E15EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2711561-B3C9-4580-B054-891CE54E15EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2711561-B3C9-4580-B054-891CE54E15EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2711561-B3C9-4580-B054-891CE54E15EE}.Release|Any CPU.Build.0 = Release|Any CPU + {321F68AE-7F97-415E-A3F9-7C477EFF95EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {321F68AE-7F97-415E-A3F9-7C477EFF95EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {321F68AE-7F97-415E-A3F9-7C477EFF95EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {321F68AE-7F97-415E-A3F9-7C477EFF95EE}.Release|Any CPU.Build.0 = Release|Any CPU + {034984CA-FB37-44AF-BBF9-EC58ED75F5F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {034984CA-FB37-44AF-BBF9-EC58ED75F5F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {034984CA-FB37-44AF-BBF9-EC58ED75F5F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {034984CA-FB37-44AF-BBF9-EC58ED75F5F3}.Release|Any CPU.Build.0 = Release|Any CPU + {0B4B02BB-0F43-4466-A369-0682281AF60E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B4B02BB-0F43-4466-A369-0682281AF60E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B4B02BB-0F43-4466-A369-0682281AF60E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B4B02BB-0F43-4466-A369-0682281AF60E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {773069DA-B66E-4667-ADCB-0D215AD8CF3E} = {DA6697E7-4DD4-45EF-90A7-2FC265855019} + {36382EFB-BE9E-45B3-BEC8-E70F65CDF868} = {DA6697E7-4DD4-45EF-90A7-2FC265855019} + {69E64B8C-4736-4334-87BF-DD631A3AD144} = {DA6697E7-4DD4-45EF-90A7-2FC265855019} + {E2711561-B3C9-4580-B054-891CE54E15EE} = {DA6697E7-4DD4-45EF-90A7-2FC265855019} + {321F68AE-7F97-415E-A3F9-7C477EFF95EE} = {DA6697E7-4DD4-45EF-90A7-2FC265855019} + {034984CA-FB37-44AF-BBF9-EC58ED75F5F3} = {DA6697E7-4DD4-45EF-90A7-2FC265855019} + {0B4B02BB-0F43-4466-A369-0682281AF60E} = {DA6697E7-4DD4-45EF-90A7-2FC265855019} + EndGlobalSection +EndGlobal diff --git a/Lidgren.Network/Documentation/ChangedFromV2.txt b/Lidgren.Network/Documentation/ChangedFromV2.txt new file mode 100644 index 0000000..edb2d45 --- /dev/null +++ b/Lidgren.Network/Documentation/ChangedFromV2.txt @@ -0,0 +1,10 @@ + + +* The NetBuffer object is gone; instead there are NetOutgoingMessage and NetIncomingMessage objects + +* No need to allocate a read buffer before calling ReadMessage + + + + + diff --git a/Lidgren.Network/Documentation/Discovery.html b/Lidgren.Network/Documentation/Discovery.html new file mode 100644 index 0000000..37bb646 --- /dev/null +++ b/Lidgren.Network/Documentation/Discovery.html @@ -0,0 +1,113 @@ + + + + + Peer/server discovery + + + + + + +
+

Peer/server discovery

+

+ Peer discovery is the process of clients detecting what servers are available. Discovery requests can be made in two ways; + locally as a broadcast, which will send a signal to all peers on your subnet. Secondly you can contact an ip address directly + and query it if a server is running. +

+

Responding to discovery requests are done in the same way regardless of how the request is made.

+ +

Here's how to do on the client side; ie. the side which makes a request:

+
+
// Enable DiscoveryResponse messages
+
config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse);
+
 
+
// Emit a discovery signal
+
Client.DiscoverLocalPeers(14242);
+
+ +

This will send a discovery signal to your subnet; Here's how to receive the signal on the server side, and send a response back to the client:

+ +
+
// Enable DiscoveryRequest messages
+
config.EnableMessageType(NetIncomingMessageType.DiscoveryRequest);
+
 
+
// Standard message reading loop
+
while ((inc = Server.ReadMessage()) != null)
+
{
+
    switch (inc.MessageType)
+
    {
+
        case NetIncomingMessageType.DiscoveryRequest:
+
 
+
            // Create a response and write some example data to it
+
            NetOutgoingMessage response = Server.CreateMessage();
+
            response.Write("My server name");
+
 
+
            // Send the response to the sender of the request
+
            Server.SendDiscoveryResponse(response, inc.SenderEndpoint);
+
            break;
+
+ +

When the response then reaches the client, you can read the data you wrote on the server:

+ +
+
// Standard message reading loop
+
while ((inc = Client.ReadMessage()) != null)
+
{
+
    switch (inc.MessageType)
+
    {
+
        case NetIncomingMessageType.DiscoveryResponse:
+
 
+
            Console.WriteLine("Found server at " + inc.SenderEndpoint + " name: " + inc.ReadString());
+
            break;
+
+
+ + diff --git a/Lidgren.Network/Documentation/Improvements.txt b/Lidgren.Network/Documentation/Improvements.txt new file mode 100644 index 0000000..a689005 --- /dev/null +++ b/Lidgren.Network/Documentation/Improvements.txt @@ -0,0 +1,22 @@ + +Improvements over last version of library: + +* New delivery type: Reliable sequenced (Lost packets are resent but late arrivals are dropped) +* Disconnects and shutdown requests are now queued properly, so calling shutdown will still send any queued messages before shutting down +* All messages are pooled/recycled for zero garbage +* Reduced CPU usage and lower latencies (in the <1 ms range, but still) due to better socket polling +* All public members of NetPeer/NetConnection are completely thread safe +* Larger number of delivery channels +* More exact roundtrip measurement +* Method serialize entire objects via reflection +* Unique identifier now exists for all peers/connections +* More flexible peer discovery; filters possible and arbitrary data can be sent with response +* Much better protection against malformed messages crashing the app + +API enhancements: +* NetPeerConfiguration immutable properties now locked once NetPeer is initialized +* Messages cannot be send twice by accident +* Impossible to confuse sending and receiving buffers since they're different classes +* No more confusion if user should create a buffer or preallocate and reuse + + diff --git a/Lidgren.Network/Documentation/PacketLayout.txt b/Lidgren.Network/Documentation/PacketLayout.txt new file mode 100644 index 0000000..1db6c8e --- /dev/null +++ b/Lidgren.Network/Documentation/PacketLayout.txt @@ -0,0 +1,17 @@ + +PER MESSAGE: +7 bits - NetMessageType +1 bit - Is a message fragment? + +[8 bits NetMessageLibraryType, if NetMessageType == Library] + +[16 bits sequence number, if NetMessageType >= UserSequenced] + +8/16 bits - Payload length in bits (variable size ushort) + +[16 bits fragments group id, if fragmented] +[16 bits fragments total count, if fragmented] +[16 bits fragment number, if fragmented] + +[x - Payload] if length > 0 + diff --git a/Lidgren.Network/Documentation/SimulatingBadNetwork.html b/Lidgren.Network/Documentation/SimulatingBadNetwork.html new file mode 100644 index 0000000..6fbf6df --- /dev/null +++ b/Lidgren.Network/Documentation/SimulatingBadNetwork.html @@ -0,0 +1,115 @@ + + + + + Lidgren tutorial + + + + + + +
+

Simulating bad network conditions

+

+ On the internet, your packets are likely to run in to all kinds of trouble. They will be delayed and lost and they might even arrive multiple times at the destination. Lidgren has a few option to simulate how your application or game will react when this happens.
+ They are all configured using the NetPeerConfiguration class - these properties exists:

+

+

+ + + + + + + + + + + + + + + + + + + + + + +
+ SimulatedLoss + +   + + This is a float which simulates lost packets. A value of 0 will disable this feature, a value of 0.5f will make half of your sent packets disappear, chosen randomly. Note that packets may contain several messages - this is the amount of packets lost. +
+   +
+ SimulatedDuplicatesChance + +   + + This is a float which determines the chance that a packet will be duplicated at the destination. 0 means no packets will be duplicated, 0.5f means that on average, every other packet will be duplicated. +
+   +
+ SimulatedMinimumLatency
+ SimulatedRandomLatency +
+   + + These two properties control simulating delay of packets in seconds (not milliseconds, use 0.05 for 50 ms of lag). They work on top of the actual network delay and the total delay will be:
+ Actual one way latency + SimulatedMinimumLatency + [Randomly per packet 0 to SimulatedRandomLatency seconds] +
+ +
+

It's recommended to assume symmetric condtions and configure server and client with the same simulation settings.

+

Simulating bad network conditions only works in DEBUG builds.

+ +
+ + diff --git a/Lidgren.Network/Documentation/TODO.txt b/Lidgren.Network/Documentation/TODO.txt new file mode 100644 index 0000000..4f693f1 --- /dev/null +++ b/Lidgren.Network/Documentation/TODO.txt @@ -0,0 +1,17 @@ + +Completed features: +* Message coalescing +* Peer, connection statistics +* Lag, loss and duplication simulation for testing +* Connection approval +* Throttling +* Clock synchronization to detect jitter per packet (NetTime.RemoteNow) +* Peer discovery +* Message fragmentation + +Missing features: +* Receipts 25% done, need design +* More realistic lag/loss (lumpy) +* Detect estimated packet loss +* More advanced ack packet + diff --git a/Lidgren.Network/Documentation/Tutorial.html b/Lidgren.Network/Documentation/Tutorial.html new file mode 100644 index 0000000..520599d --- /dev/null +++ b/Lidgren.Network/Documentation/Tutorial.html @@ -0,0 +1,206 @@ + + + + + Lidgren basics tutorial + + + + + + +
+

+ Lidgren basics

+

+ Lidgren network library is all about messages. There are two types of messages:

+
  • Library messages telling you things like a peer has connected or error messages when unexpected things happen.
  • +
  • Data messages which is data sent from a remote (connected or unconnected) peer.
  • +

    + The base class for establishing connections, receiving and sending message are the NetPeer class. Using it you can make a peer-to-peer network, but if you are creating a server/client topology there are special classes called NetServer and NetClient. They inherit NetPeer but sets some defaults and includes some helper methods/properties.

    +

    + Here's how to set up a NetServer:

    +
    +
    NetPeerConfiguration config = new NetPeerConfiguration("MyExampleName");
    +
    config.Port = 14242;
    +
     
    +
    NetServer server = new NetServer(config);
    +
    server.Start();
    +
    +

    + The code above first creates a configuration. It has lots of properties you can change, but the default values should be pretty good for most applications. The string you provide in the constructor (MyExampleName) is an identifier to distinquish it from other applications using the lidgren library. Just make sure you use the same string in both server and client - or you will be unable to communicate between them.

    +

    + Secondly we've set the local port the server should listen to. This is the port number we tell the client(s) what port number to connect to. The local port can be set for a client too, but it's not needed and not recommended.

    +

    + Thirdly we create our server object and fourth we Start() it. Starting the server will create a new network thread and bind to a socket and start listening for connections.

    +

    + Early on we spoke about messages; now is the time to start receiving and sending some. Here's a code snippet for receiving messages:

    +
    +
    NetIncomingMessage msg;
    +
    while ((msg = server.ReadMessage()) != null)
    +
    {
    +
        switch (msg.MessageType)
    +
        {
    +
            case NetIncomingMessageType.VerboseDebugMessage:
    +
            case NetIncomingMessageType.DebugMessage:
    +
            case NetIncomingMessageType.WarningMessage:
    +
            case NetIncomingMessageType.ErrorMessage:
    +
                Console.WriteLine(msg.ReadString());
    +
                break;
    +
            default:
    +
                Console.WriteLine("Unhandled type: " + msg.MessageType);
    +
                break;
    +
        }
    +
        server.Recycle(msg);
    +
    }
    +
    +

    + So, lets dissect the above code. First we declare a NetIncomingMessage, which is the type of incoming messages. Then we read a message and handles it, looping back as long as there are messages to fetch. For each message we find, we switch on sometime called MessageType - it's a description what the message contains. In this code example we only catch messages of type VerboseDebugMessage, DebugMessage, WarningMessage and ErrorMessage. All those four types are emitted by the library to inform about various events. They all contains a single string, so we use the method ReadString() to extract a copy of that string and print it in the console.

    +

    + Reading data will increment the internal message pointer so you can read subsequent data using the Read*() methods.

    +

    + For all other message type we just print that it's currently unhandled.

    +

    + Finally, we recycle the message after we're done with it - this will enable the library to reuse the object and create less garbage.

    +

    + Sending messages are even easier:

    +
    +
    NetOutgoingMessage sendMsg = server.CreateMessage();
    +
    sendMsg.Write("Hello");
    +
    sendMsg.Write(42);
    +
     
    +
    server.SendMessage(sendMsg, recipient, NetDeliveryMethod.ReliableOrdered);
    +
    +

    + The above code first creates a new message, or uses a recycled message, which is why it's not possible to just create a message using new(). It then writes a string ("Hello") and an integer (System.Int32, 4 bytes in size) to the message.

    +

    + Then the message is sent using the SendMessage() method. The first argument is the message to send, the second argument is the recipient connection - which we'll not go into detail about just yet - and the third argument are HOW to deliver the message, or rather how to behave if network conditions are bad and a packet gets lost, duplicated or reordered.

    +

    + There are five delivery methods available:

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Unreliable + +   + + This is just UDP. Messages can be lost, received more than once and messages sent after other messages may be received before them. +
    +   +
    + UnreliableSequenced + +   + + Using this delivery method messages can still be lost; but you're protected against duplicated messages and if a message arrives late; that is, if a message sent after this one has already been received - it will be dropped. This means you will never receive "older" data than what you already have received. +
    +   +
    + ReliableUnordered + +   + + This delivery method ensures that every message sent will be received eventually. It does not however guarantee what order they will be received; late messages may be delivered before older ones. +
    +   +
    + ReliableSequenced + +   + + This delivery method is similar to UnreliableSequenced; except that is guarantees that SOME messages will be received - if you only send one message - it will be received. If you sent two messages quickly, and they get reordered in transit, only the newest message will be received - but at least ONE of them will be received guaranteed. +
    +   +
    ReliableOrdered  + This delivery method guarantees that messages will always be received in the exact order they were sent. +
    +
    +

    + Here's how to read and decode the message above:

    +
    +
    NetIncomingMessage incMsg = server.ReadMessage();
    +
    string str = incMsg.ReadString();
    +
    int a = incMsg.ReadInt32();
    +
    +
    + + diff --git a/Lidgren.Network/Lidgren.Network.csproj b/Lidgren.Network/Lidgren.Network.csproj new file mode 100644 index 0000000..872373c --- /dev/null +++ b/Lidgren.Network/Lidgren.Network.csproj @@ -0,0 +1,105 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Library + Properties + Lidgren.Network + Lidgren.Network + v3.5 + 512 + + + true + full + true + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Lidgren.Network/NetBigInteger.cs b/Lidgren.Network/NetBigInteger.cs new file mode 100644 index 0000000..1b0bdd0 --- /dev/null +++ b/Lidgren.Network/NetBigInteger.cs @@ -0,0 +1,2106 @@ +// +// BigInteger.cs - Big Integer implementation +// +// Authors: +// Ben Maurer +// Chew Keong TAN +// Sebastien Pouliot +// Pieter Philippaerts +// +// Copyright (c) 2003 Ben Maurer +// All rights reserved +// +// Copyright (c) 2002 Chew Keong TAN +// All rights reserved. +// +// Copyright (C) 2004 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class BigInteger + { + /// + /// The Length of this BigInteger + /// + uint length = 1; + + /// + /// The data for this BigInteger + /// + uint[] data; + + /// + /// Default length of a BigInteger in bytes + /// + const uint DEFAULT_LEN = 20; + + /// + /// Table of primes below 2000. + /// + /// + /// + /// This table was generated using Mathematica 4.1 using the following function: + /// + /// + /// + /// PrimeTable [x_] := Prime [Range [1, PrimePi [x]]] + /// PrimeTable [6000] + /// + /// + /// + internal static readonly uint[] smallPrimes = { + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, + 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, + 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, + 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, + 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, + 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, + 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, + 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, + 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, + + 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, + 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, + 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, + 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, + 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, + 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, + 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, + 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, + 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, + 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, + 1979, 1987, 1993, 1997, 1999, + + 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, + 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, + 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, + 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, + 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, + 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, + 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, + 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, + 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, + 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, + + 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, + 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, + 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, + 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, + 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, + 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, + 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, + 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, + 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, + 3947, 3967, 3989, + + 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, + 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, + 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, + 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, + 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, + 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, + 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, + 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, + 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, + 4993, 4999, + + 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, + 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, + 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, + 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, + 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, + 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, + 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, + 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, + 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987 + }; + + public enum Sign : int + { + Negative = -1, + Zero = 0, + Positive = 1 + }; + + const string WouldReturnNegVal = "Operation would return a negative value"; + + public BigInteger() + { + data = new uint[DEFAULT_LEN]; + this.length = DEFAULT_LEN; + } + + [CLSCompliant(false)] + public BigInteger(Sign sign, uint len) + { + this.data = new uint[len]; + this.length = len; + } + + public BigInteger(BigInteger bi) + { + this.data = (uint[])bi.data.Clone(); + this.length = bi.length; + } + + [CLSCompliant(false)] + public BigInteger(BigInteger bi, uint len) + { + + this.data = new uint[len]; + + for (uint i = 0; i < bi.length; i++) + this.data[i] = bi.data[i]; + + this.length = bi.length; + } + + public BigInteger(byte[] inData) + { + length = (uint)inData.Length >> 2; + int leftOver = inData.Length & 0x3; + + // length not multiples of 4 + if (leftOver != 0) length++; + + data = new uint[length]; + + for (int i = inData.Length - 1, j = 0; i >= 3; i -= 4, j++) + { + data[j] = (uint)( + (inData[i - 3] << (3 * 8)) | + (inData[i - 2] << (2 * 8)) | + (inData[i - 1] << (1 * 8)) | + (inData[i]) + ); + } + + switch (leftOver) + { + case 1: data[length - 1] = (uint)inData[0]; break; + case 2: data[length - 1] = (uint)((inData[0] << 8) | inData[1]); break; + case 3: data[length - 1] = (uint)((inData[0] << 16) | (inData[1] << 8) | inData[2]); break; + } + + this.Normalize(); + } + + [CLSCompliant(false)] + public BigInteger(uint[] inData) + { + length = (uint)inData.Length; + + data = new uint[length]; + + for (int i = (int)length - 1, j = 0; i >= 0; i--, j++) + data[j] = inData[i]; + + this.Normalize(); + } + + [CLSCompliant(false)] + public BigInteger(uint ui) + { + data = new uint[] { ui }; + } + + [CLSCompliant(false)] + public BigInteger(ulong ul) + { + data = new uint[2] { (uint)ul, (uint)(ul >> 32) }; + length = 2; + + this.Normalize(); + } + + [CLSCompliant(false)] + public static implicit operator BigInteger(uint value) + { + return (new BigInteger(value)); + } + + public static implicit operator BigInteger(int value) + { + if (value < 0) throw new ArgumentOutOfRangeException("value"); + return (new BigInteger((uint)value)); + } + + [CLSCompliant(false)] + public static implicit operator BigInteger(ulong value) + { + return (new BigInteger(value)); + } + + /* This is the BigInteger.Parse method I use. This method works + because BigInteger.ToString returns the input I gave to Parse. */ + public static BigInteger Parse(string number) + { + if (number == null) + throw new ArgumentNullException("number"); + + int i = 0, len = number.Length; + char c; + bool digits_seen = false; + BigInteger val = new BigInteger(0); + if (number[i] == '+') + { + i++; + } + else if (number[i] == '-') + { + throw new FormatException(WouldReturnNegVal); + } + + for (; i < len; i++) + { + c = number[i]; + if (c == '\0') + { + i = len; + continue; + } + if (c >= '0' && c <= '9') + { + val = val * 10 + (c - '0'); + digits_seen = true; + } + else + { + if (Char.IsWhiteSpace(c)) + { + for (i++; i < len; i++) + { + if (!Char.IsWhiteSpace(number[i])) + throw new FormatException(); + } + break; + } + else + throw new FormatException(); + } + } + if (!digits_seen) + throw new FormatException(); + return val; + } + + public static BigInteger operator +(BigInteger bi1, BigInteger bi2) + { + if (bi1 == 0) + return new BigInteger(bi2); + else if (bi2 == 0) + return new BigInteger(bi1); + else + return Kernel.AddSameSign(bi1, bi2); + } + + public static BigInteger operator -(BigInteger bi1, BigInteger bi2) + { + if (bi2 == 0) + return new BigInteger(bi1); + + if (bi1 == 0) + throw new ArithmeticException(WouldReturnNegVal); + + switch (Kernel.Compare(bi1, bi2)) + { + + case Sign.Zero: + return 0; + + case Sign.Positive: + return Kernel.Subtract(bi1, bi2); + + case Sign.Negative: + throw new ArithmeticException(WouldReturnNegVal); + default: + throw new Exception(); + } + } + + public static int operator %(BigInteger bi, int i) + { + if (i > 0) + return (int)Kernel.DwordMod(bi, (uint)i); + else + return -(int)Kernel.DwordMod(bi, (uint)-i); + } + +#if !INSIDE_CORLIB + [CLSCompliant(false)] +#endif + public static uint operator %(BigInteger bi, uint ui) + { + return Kernel.DwordMod(bi, (uint)ui); + } + + public static BigInteger operator %(BigInteger bi1, BigInteger bi2) + { + return Kernel.multiByteDivide(bi1, bi2)[1]; + } + + public static BigInteger operator /(BigInteger bi, int i) + { + if (i > 0) + return Kernel.DwordDiv(bi, (uint)i); + + throw new ArithmeticException(WouldReturnNegVal); + } + + public static BigInteger operator /(BigInteger bi1, BigInteger bi2) + { + return Kernel.multiByteDivide(bi1, bi2)[0]; + } + + public static BigInteger operator *(BigInteger bi1, BigInteger bi2) + { + if (bi1 == 0 || bi2 == 0) return 0; + + // + // Validate pointers + // + if (bi1.data.Length < bi1.length) throw new IndexOutOfRangeException("bi1 out of range"); + if (bi2.data.Length < bi2.length) throw new IndexOutOfRangeException("bi2 out of range"); + + BigInteger ret = new BigInteger(Sign.Positive, bi1.length + bi2.length); + + Kernel.Multiply(bi1.data, 0, bi1.length, bi2.data, 0, bi2.length, ret.data, 0); + + ret.Normalize(); + return ret; + } + + public static BigInteger operator *(BigInteger bi, int i) + { + if (i < 0) throw new ArithmeticException(WouldReturnNegVal); + if (i == 0) return 0; + if (i == 1) return new BigInteger(bi); + + return Kernel.MultiplyByDword(bi, (uint)i); + } + + public static BigInteger operator <<(BigInteger bi1, int shiftVal) + { + return Kernel.LeftShift(bi1, shiftVal); + } + + public static BigInteger operator >>(BigInteger bi1, int shiftVal) + { + return Kernel.RightShift(bi1, shiftVal); + } + + // with names suggested by FxCop 1.30 + + public static BigInteger Add(BigInteger bi1, BigInteger bi2) + { + return (bi1 + bi2); + } + + public static BigInteger Subtract(BigInteger bi1, BigInteger bi2) + { + return (bi1 - bi2); + } + + public static int Modulus(BigInteger bi, int i) + { + return (bi % i); + } + +#if !INSIDE_CORLIB + [CLSCompliant(false)] +#endif + public static uint Modulus(BigInteger bi, uint ui) + { + return (bi % ui); + } + + public static BigInteger Modulus(BigInteger bi1, BigInteger bi2) + { + return (bi1 % bi2); + } + + public static BigInteger Divid(BigInteger bi, int i) + { + return (bi / i); + } + + public static BigInteger Divid(BigInteger bi1, BigInteger bi2) + { + return (bi1 / bi2); + } + + public static BigInteger Multiply(BigInteger bi1, BigInteger bi2) + { + return (bi1 * bi2); + } + + public static BigInteger Multiply(BigInteger bi, int i) + { + return (bi * i); + } + + public int BitCount() + { + this.Normalize(); + + uint value = data[length - 1]; + uint mask = 0x80000000; + uint bits = 32; + + while (bits > 0 && (value & mask) == 0) + { + bits--; + mask >>= 1; + } + bits += ((length - 1) << 5); + + return (int)bits; + } + + /// + /// Tests if the specified bit is 1. + /// + /// The bit to test. The least significant bit is 0. + /// True if bitNum is set to 1, else false. + [CLSCompliant(false)] + public bool TestBit(uint bitNum) + { + uint bytePos = bitNum >> 5; // divide by 32 + byte bitPos = (byte)(bitNum & 0x1F); // get the lowest 5 bits + + uint mask = (uint)1 << bitPos; + return ((this.data[bytePos] & mask) != 0); + } + + public bool TestBit(int bitNum) + { + if (bitNum < 0) throw new IndexOutOfRangeException("bitNum out of range"); + + uint bytePos = (uint)bitNum >> 5; // divide by 32 + byte bitPos = (byte)(bitNum & 0x1F); // get the lowest 5 bits + + uint mask = (uint)1 << bitPos; + return ((this.data[bytePos] | mask) == this.data[bytePos]); + } + + [CLSCompliant(false)] + public void SetBit(uint bitNum) + { + SetBit(bitNum, true); + } + + [CLSCompliant(false)] + public void ClearBit(uint bitNum) + { + SetBit(bitNum, false); + } + + [CLSCompliant(false)] + public void SetBit(uint bitNum, bool value) + { + uint bytePos = bitNum >> 5; // divide by 32 + + if (bytePos < this.length) + { + uint mask = (uint)1 << (int)(bitNum & 0x1F); + if (value) + this.data[bytePos] |= mask; + else + this.data[bytePos] &= ~mask; + } + } + + public int LowestSetBit() + { + if (this == 0) return -1; + int i = 0; + while (!TestBit(i)) i++; + return i; + } + + public byte[] GetBytes() + { + if (this == 0) return new byte[1]; + + int numBits = BitCount(); + int numBytes = numBits >> 3; + if ((numBits & 0x7) != 0) + numBytes++; + + byte[] result = new byte[numBytes]; + + int numBytesInWord = numBytes & 0x3; + if (numBytesInWord == 0) numBytesInWord = 4; + + int pos = 0; + for (int i = (int)length - 1; i >= 0; i--) + { + uint val = data[i]; + for (int j = numBytesInWord - 1; j >= 0; j--) + { + result[pos + j] = (byte)(val & 0xFF); + val >>= 8; + } + pos += numBytesInWord; + numBytesInWord = 4; + } + return result; + } + + [CLSCompliant(false)] + public static bool operator ==(BigInteger bi1, uint ui) + { + if (bi1.length != 1) bi1.Normalize(); + return bi1.length == 1 && bi1.data[0] == ui; + } + + [CLSCompliant(false)] + public static bool operator !=(BigInteger bi1, uint ui) + { + if (bi1.length != 1) bi1.Normalize(); + return !(bi1.length == 1 && bi1.data[0] == ui); + } + + public static bool operator ==(BigInteger bi1, BigInteger bi2) + { + // we need to compare with null + if ((bi1 as object) == (bi2 as object)) + return true; + if (null == bi1 || null == bi2) + return false; + return Kernel.Compare(bi1, bi2) == 0; + } + + public static bool operator !=(BigInteger bi1, BigInteger bi2) + { + // we need to compare with null + if ((bi1 as object) == (bi2 as object)) + return false; + if (null == bi1 || null == bi2) + return true; + return Kernel.Compare(bi1, bi2) != 0; + } + + public static bool operator >(BigInteger bi1, BigInteger bi2) + { + return Kernel.Compare(bi1, bi2) > 0; + } + + public static bool operator <(BigInteger bi1, BigInteger bi2) + { + return Kernel.Compare(bi1, bi2) < 0; + } + + public static bool operator >=(BigInteger bi1, BigInteger bi2) + { + return Kernel.Compare(bi1, bi2) >= 0; + } + + public static bool operator <=(BigInteger bi1, BigInteger bi2) + { + return Kernel.Compare(bi1, bi2) <= 0; + } + + public Sign Compare(BigInteger bi) + { + return Kernel.Compare(this, bi); + } + + [CLSCompliant(false)] + public string ToString(uint radix) + { + return ToString(radix, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + } + + [CLSCompliant(false)] + public string ToString(uint radix, string characterSet) + { + if (characterSet.Length < radix) + throw new ArgumentException("charSet length less than radix", "characterSet"); + if (radix == 1) + throw new ArgumentException("There is no such thing as radix one notation", "radix"); + + if (this == 0) return "0"; + if (this == 1) return "1"; + + string result = ""; + + BigInteger a = new BigInteger(this); + + while (a != 0) + { + uint rem = Kernel.SingleByteDivideInPlace(a, radix); + result = characterSet[(int)rem] + result; + } + + return result; + } + + /// + /// Normalizes this by setting the length to the actual number of + /// uints used in data and by setting the sign to Sign.Zero if the + /// value of this is 0. + /// + private void Normalize() + { + // Normalize length + while (length > 0 && data[length - 1] == 0) length--; + + // Check for zero + if (length == 0) + length++; + } + + public void Clear() + { + for (int i = 0; i < length; i++) + data[i] = 0x00; + } + + public override int GetHashCode() + { + uint val = 0; + + for (uint i = 0; i < this.length; i++) + val ^= this.data[i]; + + return (int)val; + } + + public override string ToString() + { + return ToString(10); + } + + public override bool Equals(object o) + { + if (o == null) return false; + if (o is int) return (int)o >= 0 && this == (uint)o; + + return Kernel.Compare(this, (BigInteger)o) == 0; + } + + public BigInteger GCD(BigInteger bi) + { + return Kernel.gcd(this, bi); + } + + public BigInteger ModInverse(BigInteger modulus) + { + return Kernel.modInverse(this, modulus); + } + + public BigInteger ModPow(BigInteger exp, BigInteger n) + { + ModulusRing mr = new ModulusRing(n); + return mr.Pow(this, exp); + } + + public sealed class ModulusRing + { + BigInteger mod, constant; + + public ModulusRing(BigInteger modulus) + { + this.mod = modulus; + + // calculate constant = b^ (2k) / m + uint i = mod.length << 1; + + constant = new BigInteger(Sign.Positive, i + 1); + constant.data[i] = 0x00000001; + + constant = constant / mod; + } + + public void BarrettReduction(BigInteger x) + { + BigInteger n = mod; + uint k = n.length, + kPlusOne = k + 1, + kMinusOne = k - 1; + + // x < mod, so nothing to do. + if (x.length < k) return; + + BigInteger q3; + + // + // Validate pointers + // + if (x.data.Length < x.length) throw new IndexOutOfRangeException("x out of range"); + + // q1 = x / b^ (k-1) + // q2 = q1 * constant + // q3 = q2 / b^ (k+1), Needs to be accessed with an offset of kPlusOne + + // TODO: We should the method in HAC p 604 to do this (14.45) + q3 = new BigInteger(Sign.Positive, x.length - kMinusOne + constant.length); + Kernel.Multiply(x.data, kMinusOne, x.length - kMinusOne, constant.data, 0, constant.length, q3.data, 0); + + // r1 = x mod b^ (k+1) + // i.e. keep the lowest (k+1) words + + uint lengthToCopy = (x.length > kPlusOne) ? kPlusOne : x.length; + + x.length = lengthToCopy; + x.Normalize(); + + // r2 = (q3 * n) mod b^ (k+1) + // partial multiplication of q3 and n + + BigInteger r2 = new BigInteger(Sign.Positive, kPlusOne); + Kernel.MultiplyMod2p32pmod(q3.data, (int)kPlusOne, (int)q3.length - (int)kPlusOne, n.data, 0, (int)n.length, r2.data, 0, (int)kPlusOne); + + r2.Normalize(); + + if (r2 <= x) + { + Kernel.MinusEq(x, r2); + } + else + { + BigInteger val = new BigInteger(Sign.Positive, kPlusOne + 1); + val.data[kPlusOne] = 0x00000001; + + Kernel.MinusEq(val, r2); + Kernel.PlusEq(x, val); + } + + while (x >= n) + Kernel.MinusEq(x, n); + } + + public BigInteger Multiply(BigInteger a, BigInteger b) + { + if (a == 0 || b == 0) return 0; + + if (a.length >= mod.length << 1) + a %= mod; + + if (b.length >= mod.length << 1) + b %= mod; + + if (a.length >= mod.length) + BarrettReduction(a); + + if (b.length >= mod.length) + BarrettReduction(b); + + BigInteger ret = new BigInteger(a * b); + BarrettReduction(ret); + + return ret; + } + + public BigInteger Difference(BigInteger a, BigInteger b) + { + Sign cmp = Kernel.Compare(a, b); + BigInteger diff; + + switch (cmp) + { + case Sign.Zero: + return 0; + case Sign.Positive: + diff = a - b; break; + case Sign.Negative: + diff = b - a; break; + default: + throw new Exception(); + } + + if (diff >= mod) + { + if (diff.length >= mod.length << 1) + diff %= mod; + else + BarrettReduction(diff); + } + if (cmp == Sign.Negative) + diff = mod - diff; + return diff; + } + + public BigInteger Pow(BigInteger b, BigInteger exp) + { + if ((mod.data[0] & 1) == 1) return OddPow(b, exp); + else return EvenPow(b, exp); + } + + public BigInteger EvenPow(BigInteger b, BigInteger exp) + { + BigInteger resultNum = new BigInteger((BigInteger)1, mod.length << 1); + BigInteger tempNum = new BigInteger(b % mod, mod.length << 1); // ensures (tempNum * tempNum) < b^ (2k) + + uint totalBits = (uint)exp.BitCount(); + + uint[] wkspace = new uint[mod.length << 1]; + + // perform squaring and multiply exponentiation + for (uint pos = 0; pos < totalBits; pos++) + { + if (exp.TestBit(pos)) + { + + Array.Clear(wkspace, 0, wkspace.Length); + Kernel.Multiply(resultNum.data, 0, resultNum.length, tempNum.data, 0, tempNum.length, wkspace, 0); + resultNum.length += tempNum.length; + uint[] t = wkspace; + wkspace = resultNum.data; + resultNum.data = t; + + BarrettReduction(resultNum); + } + + Kernel.SquarePositive(tempNum, ref wkspace); + BarrettReduction(tempNum); + + if (tempNum == 1) + { + return resultNum; + } + } + + return resultNum; + } + + private BigInteger OddPow(BigInteger b, BigInteger exp) + { + BigInteger resultNum = new BigInteger(Montgomery.ToMont(1, mod), mod.length << 1); + BigInteger tempNum = new BigInteger(Montgomery.ToMont(b, mod), mod.length << 1); // ensures (tempNum * tempNum) < b^ (2k) + uint mPrime = Montgomery.Inverse(mod.data[0]); + uint totalBits = (uint)exp.BitCount(); + + uint[] wkspace = new uint[mod.length << 1]; + + // perform squaring and multiply exponentiation + for (uint pos = 0; pos < totalBits; pos++) + { + if (exp.TestBit(pos)) + { + + Array.Clear(wkspace, 0, wkspace.Length); + Kernel.Multiply(resultNum.data, 0, resultNum.length, tempNum.data, 0, tempNum.length, wkspace, 0); + resultNum.length += tempNum.length; + uint[] t = wkspace; + wkspace = resultNum.data; + resultNum.data = t; + + Montgomery.Reduce(resultNum, mod, mPrime); + } + + Kernel.SquarePositive(tempNum, ref wkspace); + Montgomery.Reduce(tempNum, mod, mPrime); + } + + Montgomery.Reduce(resultNum, mod, mPrime); + return resultNum; + } + + // TODO: Make tests for this, not really needed b/c prime stuff + // checks it, but still would be nice + + [CLSCompliant(false)] + public BigInteger Pow(uint b, BigInteger exp) + { + // if (b != 2) { + if ((mod.data[0] & 1) == 1) + return OddPow(b, exp); + else + return EvenPow(b, exp); + /* buggy in some cases (like the well tested primes) + } else { + if ((mod.data [0] & 1) == 1) + return OddModTwoPow (exp); + else + return EvenModTwoPow (exp); + }*/ + } + + private unsafe BigInteger OddPow(uint b, BigInteger exp) + { + exp.Normalize(); + uint[] wkspace = new uint[mod.length << 1 + 1]; + + BigInteger resultNum = Montgomery.ToMont((BigInteger)b, this.mod); + resultNum = new BigInteger(resultNum, mod.length << 1 + 1); + + uint mPrime = Montgomery.Inverse(mod.data[0]); + + uint pos = (uint)exp.BitCount() - 2; + + // + // We know that the first itr will make the val b + // + + do + { + // + // r = r ^ 2 % m + // + Kernel.SquarePositive(resultNum, ref wkspace); + resultNum = Montgomery.Reduce(resultNum, mod, mPrime); + + if (exp.TestBit(pos)) + { + + // + // r = r * b % m + // + + // TODO: Is Unsafe really speeding things up? + fixed (uint* u = resultNum.data) + { + + uint i = 0; + ulong mc = 0; + + do + { + mc += (ulong)u[i] * (ulong)b; + u[i] = (uint)mc; + mc >>= 32; + } while (++i < resultNum.length); + + if (resultNum.length < mod.length) + { + if (mc != 0) + { + u[i] = (uint)mc; + resultNum.length++; + while (resultNum >= mod) + Kernel.MinusEq(resultNum, mod); + } + } + else if (mc != 0) + { + + // + // First, we estimate the quotient by dividing + // the first part of each of the numbers. Then + // we correct this, if necessary, with a subtraction. + // + + uint cc = (uint)mc; + + // We would rather have this estimate overshoot, + // so we add one to the divisor + uint divEstimate; + if (mod.data[mod.length - 1] < UInt32.MaxValue) + { + divEstimate = (uint)((((ulong)cc << 32) | (ulong)u[i - 1]) / + (mod.data[mod.length - 1] + 1)); + } + else + { + // guess but don't divide by 0 + divEstimate = (uint)((((ulong)cc << 32) | (ulong)u[i - 1]) / + (mod.data[mod.length - 1])); + } + + uint t; + + i = 0; + mc = 0; + do + { + mc += (ulong)mod.data[i] * (ulong)divEstimate; + t = u[i]; + u[i] -= (uint)mc; + mc >>= 32; + if (u[i] > t) mc++; + i++; + } while (i < resultNum.length); + cc -= (uint)mc; + + if (cc != 0) + { + + uint sc = 0, j = 0; + uint[] s = mod.data; + do + { + uint a = s[j]; + if (((a += sc) < sc) | ((u[j] -= a) > ~a)) sc = 1; + else sc = 0; + j++; + } while (j < resultNum.length); + cc -= sc; + } + while (resultNum >= mod) + Kernel.MinusEq(resultNum, mod); + } + else + { + while (resultNum >= mod) + Kernel.MinusEq(resultNum, mod); + } + } + } + } while (pos-- > 0); + + resultNum = Montgomery.Reduce(resultNum, mod, mPrime); + return resultNum; + + } + + private unsafe BigInteger EvenPow(uint b, BigInteger exp) + { + exp.Normalize(); + uint[] wkspace = new uint[mod.length << 1 + 1]; + BigInteger resultNum = new BigInteger((BigInteger)b, mod.length << 1 + 1); + + uint pos = (uint)exp.BitCount() - 2; + + // + // We know that the first itr will make the val b + // + + do + { + // + // r = r ^ 2 % m + // + Kernel.SquarePositive(resultNum, ref wkspace); + if (!(resultNum.length < mod.length)) + BarrettReduction(resultNum); + + if (exp.TestBit(pos)) + { + + // + // r = r * b % m + // + + // TODO: Is Unsafe really speeding things up? + fixed (uint* u = resultNum.data) + { + + uint i = 0; + ulong mc = 0; + + do + { + mc += (ulong)u[i] * (ulong)b; + u[i] = (uint)mc; + mc >>= 32; + } while (++i < resultNum.length); + + if (resultNum.length < mod.length) + { + if (mc != 0) + { + u[i] = (uint)mc; + resultNum.length++; + while (resultNum >= mod) + Kernel.MinusEq(resultNum, mod); + } + } + else if (mc != 0) + { + + // + // First, we estimate the quotient by dividing + // the first part of each of the numbers. Then + // we correct this, if necessary, with a subtraction. + // + + uint cc = (uint)mc; + + // We would rather have this estimate overshoot, + // so we add one to the divisor + uint divEstimate = (uint)((((ulong)cc << 32) | (ulong)u[i - 1]) / + (mod.data[mod.length - 1] + 1)); + + uint t; + + i = 0; + mc = 0; + do + { + mc += (ulong)mod.data[i] * (ulong)divEstimate; + t = u[i]; + u[i] -= (uint)mc; + mc >>= 32; + if (u[i] > t) mc++; + i++; + } while (i < resultNum.length); + cc -= (uint)mc; + + if (cc != 0) + { + + uint sc = 0, j = 0; + uint[] s = mod.data; + do + { + uint a = s[j]; + if (((a += sc) < sc) | ((u[j] -= a) > ~a)) sc = 1; + else sc = 0; + j++; + } while (j < resultNum.length); + cc -= sc; + } + while (resultNum >= mod) + Kernel.MinusEq(resultNum, mod); + } + else + { + while (resultNum >= mod) + Kernel.MinusEq(resultNum, mod); + } + } + } + } while (pos-- > 0); + + return resultNum; + } + } + + internal sealed class Montgomery + { + + private Montgomery() + { + } + + public static uint Inverse(uint n) + { + uint y = n, z; + + while ((z = n * y) != 1) + y *= 2 - z; + + return (uint)-y; + } + + public static BigInteger ToMont(BigInteger n, BigInteger m) + { + n.Normalize(); m.Normalize(); + + n <<= (int)m.length * 32; + n %= m; + return n; + } + + public static unsafe BigInteger Reduce(BigInteger n, BigInteger m, uint mPrime) + { + BigInteger A = n; + fixed (uint* a = A.data, mm = m.data) + { + for (uint i = 0; i < m.length; i++) + { + // The mod here is taken care of by the CPU, + // since the multiply will overflow. + uint u_i = a[0] * mPrime /* % 2^32 */; + + // + // A += u_i * m; + // A >>= 32 + // + + // mP = Position in mod + // aSP = the source of bits from a + // aDP = destination for bits + uint* mP = mm, aSP = a, aDP = a; + + ulong c = (ulong)u_i * ((ulong)*(mP++)) + *(aSP++); + c >>= 32; + uint j = 1; + + // Multiply and add + for (; j < m.length; j++) + { + c += (ulong)u_i * (ulong)*(mP++) + *(aSP++); + *(aDP++) = (uint)c; + c >>= 32; + } + + // Account for carry + // TODO: use a better loop here, we dont need the ulong stuff + for (; j < A.length; j++) + { + c += *(aSP++); + *(aDP++) = (uint)c; + c >>= 32; + if (c == 0) { j++; break; } + } + // Copy the rest + for (; j < A.length; j++) + { + *(aDP++) = *(aSP++); + } + + *(aDP++) = (uint)c; + } + + while (A.length > 1 && a[A.length - 1] == 0) A.length--; + + } + if (A >= m) Kernel.MinusEq(A, m); + + return A; + } + } + + /// + /// Low level functions for the BigInteger + /// + private sealed class Kernel + { + /// + /// Adds two numbers with the same sign. + /// + /// A BigInteger + /// A BigInteger + /// bi1 + bi2 + public static BigInteger AddSameSign(BigInteger bi1, BigInteger bi2) + { + uint[] x, y; + uint yMax, xMax, i = 0; + + // x should be bigger + if (bi1.length < bi2.length) + { + x = bi2.data; + xMax = bi2.length; + y = bi1.data; + yMax = bi1.length; + } + else + { + x = bi1.data; + xMax = bi1.length; + y = bi2.data; + yMax = bi2.length; + } + + BigInteger result = new BigInteger(Sign.Positive, xMax + 1); + + uint[] r = result.data; + + ulong sum = 0; + + // Add common parts of both numbers + do + { + sum = ((ulong)x[i]) + ((ulong)y[i]) + sum; + r[i] = (uint)sum; + sum >>= 32; + } while (++i < yMax); + + // Copy remainder of longer number while carry propagation is required + bool carry = (sum != 0); + + if (carry) + { + + if (i < xMax) + { + do + carry = ((r[i] = x[i] + 1) == 0); + while (++i < xMax && carry); + } + + if (carry) + { + r[i] = 1; + result.length = ++i; + return result; + } + } + + // Copy the rest + if (i < xMax) + { + do + r[i] = x[i]; + while (++i < xMax); + } + + result.Normalize(); + return result; + } + + public static BigInteger Subtract(BigInteger big, BigInteger small) + { + BigInteger result = new BigInteger(Sign.Positive, big.length); + + uint[] r = result.data, b = big.data, s = small.data; + uint i = 0, c = 0; + + do + { + + uint x = s[i]; + if (((x += c) < c) | ((r[i] = b[i] - x) > ~x)) + c = 1; + else + c = 0; + + } while (++i < small.length); + + if (i == big.length) goto fixup; + + if (c == 1) + { + do + r[i] = b[i] - 1; + while (b[i++] == 0 && i < big.length); + + if (i == big.length) goto fixup; + } + + do + r[i] = b[i]; + while (++i < big.length); + + fixup: + + result.Normalize(); + return result; + } + + public static void MinusEq(BigInteger big, BigInteger small) + { + uint[] b = big.data, s = small.data; + uint i = 0, c = 0; + + do + { + uint x = s[i]; + if (((x += c) < c) | ((b[i] -= x) > ~x)) + c = 1; + else + c = 0; + } while (++i < small.length); + + if (i == big.length) goto fixup; + + if (c == 1) + { + do + b[i]--; + while (b[i++] == 0 && i < big.length); + } + + fixup: + + // Normalize length + while (big.length > 0 && big.data[big.length - 1] == 0) big.length--; + + // Check for zero + if (big.length == 0) + big.length++; + + } + + public static void PlusEq(BigInteger bi1, BigInteger bi2) + { + uint[] x, y; + uint yMax, xMax, i = 0; + bool flag = false; + + // x should be bigger + if (bi1.length < bi2.length) + { + flag = true; + x = bi2.data; + xMax = bi2.length; + y = bi1.data; + yMax = bi1.length; + } + else + { + x = bi1.data; + xMax = bi1.length; + y = bi2.data; + yMax = bi2.length; + } + + uint[] r = bi1.data; + + ulong sum = 0; + + // Add common parts of both numbers + do + { + sum += ((ulong)x[i]) + ((ulong)y[i]); + r[i] = (uint)sum; + sum >>= 32; + } while (++i < yMax); + + // Copy remainder of longer number while carry propagation is required + bool carry = (sum != 0); + + if (carry) + { + + if (i < xMax) + { + do + carry = ((r[i] = x[i] + 1) == 0); + while (++i < xMax && carry); + } + + if (carry) + { + r[i] = 1; + bi1.length = ++i; + return; + } + } + + // Copy the rest + if (flag && i < xMax - 1) + { + do + r[i] = x[i]; + while (++i < xMax); + } + + bi1.length = xMax + 1; + bi1.Normalize(); + } + + /// + /// Compares two BigInteger + /// + /// A BigInteger + /// A BigInteger + /// The sign of bi1 - bi2 + public static Sign Compare(BigInteger bi1, BigInteger bi2) + { + // + // Step 1. Compare the lengths + // + uint l1 = bi1.length, l2 = bi2.length; + + while (l1 > 0 && bi1.data[l1 - 1] == 0) l1--; + while (l2 > 0 && bi2.data[l2 - 1] == 0) l2--; + + if (l1 == 0 && l2 == 0) return Sign.Zero; + + // bi1 len < bi2 len + if (l1 < l2) return Sign.Negative; + // bi1 len > bi2 len + else if (l1 > l2) return Sign.Positive; + + // + // Step 2. Compare the bits + // + + uint pos = l1 - 1; + + while (pos != 0 && bi1.data[pos] == bi2.data[pos]) pos--; + + if (bi1.data[pos] < bi2.data[pos]) + return Sign.Negative; + else if (bi1.data[pos] > bi2.data[pos]) + return Sign.Positive; + else + return Sign.Zero; + } + + /// + /// Performs n / d and n % d in one operation. + /// + /// A BigInteger, upon exit this will hold n / d + /// The divisor + /// n % d + public static uint SingleByteDivideInPlace(BigInteger n, uint d) + { + ulong r = 0; + uint i = n.length; + + while (i-- > 0) + { + r <<= 32; + r |= n.data[i]; + n.data[i] = (uint)(r / d); + r %= d; + } + n.Normalize(); + + return (uint)r; + } + + public static uint DwordMod(BigInteger n, uint d) + { + ulong r = 0; + uint i = n.length; + + while (i-- > 0) + { + r <<= 32; + r |= n.data[i]; + r %= d; + } + + return (uint)r; + } + + public static BigInteger DwordDiv(BigInteger n, uint d) + { + BigInteger ret = new BigInteger(Sign.Positive, n.length); + + ulong r = 0; + uint i = n.length; + + while (i-- > 0) + { + r <<= 32; + r |= n.data[i]; + ret.data[i] = (uint)(r / d); + r %= d; + } + ret.Normalize(); + + return ret; + } + + public static BigInteger[] DwordDivMod(BigInteger n, uint d) + { + BigInteger ret = new BigInteger(Sign.Positive, n.length); + + ulong r = 0; + uint i = n.length; + + while (i-- > 0) + { + r <<= 32; + r |= n.data[i]; + ret.data[i] = (uint)(r / d); + r %= d; + } + ret.Normalize(); + + BigInteger rem = (uint)r; + + return new BigInteger[] { ret, rem }; + } + + public static BigInteger[] multiByteDivide(BigInteger bi1, BigInteger bi2) + { + if (Kernel.Compare(bi1, bi2) == Sign.Negative) + return new BigInteger[2] { 0, new BigInteger(bi1) }; + + bi1.Normalize(); bi2.Normalize(); + + if (bi2.length == 1) + return DwordDivMod(bi1, bi2.data[0]); + + uint remainderLen = bi1.length + 1; + int divisorLen = (int)bi2.length + 1; + + uint mask = 0x80000000; + uint val = bi2.data[bi2.length - 1]; + int shift = 0; + int resultPos = (int)bi1.length - (int)bi2.length; + + while (mask != 0 && (val & mask) == 0) + { + shift++; mask >>= 1; + } + + BigInteger quot = new BigInteger(Sign.Positive, bi1.length - bi2.length + 1); + BigInteger rem = (bi1 << shift); + + uint[] remainder = rem.data; + + bi2 = bi2 << shift; + + int j = (int)(remainderLen - bi2.length); + int pos = (int)remainderLen - 1; + + uint firstDivisorByte = bi2.data[bi2.length - 1]; + ulong secondDivisorByte = bi2.data[bi2.length - 2]; + + while (j > 0) + { + ulong dividend = ((ulong)remainder[pos] << 32) + (ulong)remainder[pos - 1]; + + ulong q_hat = dividend / (ulong)firstDivisorByte; + ulong r_hat = dividend % (ulong)firstDivisorByte; + + do + { + + if (q_hat == 0x100000000 || + (q_hat * secondDivisorByte) > ((r_hat << 32) + remainder[pos - 2])) + { + q_hat--; + r_hat += (ulong)firstDivisorByte; + + if (r_hat < 0x100000000) + continue; + } + break; + } while (true); + + // + // At this point, q_hat is either exact, or one too large + // (more likely to be exact) so, we attempt to multiply the + // divisor by q_hat, if we get a borrow, we just subtract + // one from q_hat and add the divisor back. + // + + uint t; + uint dPos = 0; + int nPos = pos - divisorLen + 1; + ulong mc = 0; + uint uint_q_hat = (uint)q_hat; + do + { + mc += (ulong)bi2.data[dPos] * (ulong)uint_q_hat; + t = remainder[nPos]; + remainder[nPos] -= (uint)mc; + mc >>= 32; + if (remainder[nPos] > t) mc++; + dPos++; nPos++; + } while (dPos < divisorLen); + + nPos = pos - divisorLen + 1; + dPos = 0; + + // Overestimate + if (mc != 0) + { + uint_q_hat--; + ulong sum = 0; + + do + { + sum = ((ulong)remainder[nPos]) + ((ulong)bi2.data[dPos]) + sum; + remainder[nPos] = (uint)sum; + sum >>= 32; + dPos++; nPos++; + } while (dPos < divisorLen); + + } + + quot.data[resultPos--] = (uint)uint_q_hat; + + pos--; + j--; + } + + quot.Normalize(); + rem.Normalize(); + BigInteger[] ret = new BigInteger[2] { quot, rem }; + + if (shift != 0) + ret[1] >>= shift; + + return ret; + } + + public static BigInteger LeftShift(BigInteger bi, int n) + { + if (n == 0) return new BigInteger(bi, bi.length + 1); + + int w = n >> 5; + n &= ((1 << 5) - 1); + + BigInteger ret = new BigInteger(Sign.Positive, bi.length + 1 + (uint)w); + + uint i = 0, l = bi.length; + if (n != 0) + { + uint x, carry = 0; + while (i < l) + { + x = bi.data[i]; + ret.data[i + w] = (x << n) | carry; + carry = x >> (32 - n); + i++; + } + ret.data[i + w] = carry; + } + else + { + while (i < l) + { + ret.data[i + w] = bi.data[i]; + i++; + } + } + + ret.Normalize(); + return ret; + } + + public static BigInteger RightShift(BigInteger bi, int n) + { + if (n == 0) return new BigInteger(bi); + + int w = n >> 5; + int s = n & ((1 << 5) - 1); + + BigInteger ret = new BigInteger(Sign.Positive, bi.length - (uint)w + 1); + uint l = (uint)ret.data.Length - 1; + + if (s != 0) + { + + uint x, carry = 0; + + while (l-- > 0) + { + x = bi.data[l + w]; + ret.data[l] = (x >> n) | carry; + carry = x << (32 - n); + } + } + else + { + while (l-- > 0) + ret.data[l] = bi.data[l + w]; + + } + ret.Normalize(); + return ret; + } + + public static BigInteger MultiplyByDword(BigInteger n, uint f) + { + BigInteger ret = new BigInteger(Sign.Positive, n.length + 1); + + uint i = 0; + ulong c = 0; + + do + { + c += (ulong)n.data[i] * (ulong)f; + ret.data[i] = (uint)c; + c >>= 32; + } while (++i < n.length); + ret.data[i] = (uint)c; + ret.Normalize(); + return ret; + + } + + /// + /// Multiplies the data in x [xOffset:xOffset+xLen] by + /// y [yOffset:yOffset+yLen] and puts it into + /// d [dOffset:dOffset+xLen+yLen]. + /// + /// + /// This code is unsafe! It is the caller's responsibility to make + /// sure that it is safe to access x [xOffset:xOffset+xLen], + /// y [yOffset:yOffset+yLen], and d [dOffset:dOffset+xLen+yLen]. + /// + public static unsafe void Multiply(uint[] x, uint xOffset, uint xLen, uint[] y, uint yOffset, uint yLen, uint[] d, uint dOffset) + { + fixed (uint* xx = x, yy = y, dd = d) + { + uint* xP = xx + xOffset, + xE = xP + xLen, + yB = yy + yOffset, + yE = yB + yLen, + dB = dd + dOffset; + + for (; xP < xE; xP++, dB++) + { + + if (*xP == 0) continue; + + ulong mcarry = 0; + + uint* dP = dB; + for (uint* yP = yB; yP < yE; yP++, dP++) + { + mcarry += ((ulong)*xP * (ulong)*yP) + (ulong)*dP; + + *dP = (uint)mcarry; + mcarry >>= 32; + } + + if (mcarry != 0) + *dP = (uint)mcarry; + } + } + } + + /// + /// Multiplies the data in x [xOffset:xOffset+xLen] by + /// y [yOffset:yOffset+yLen] and puts the low mod words into + /// d [dOffset:dOffset+mod]. + /// + /// + /// This code is unsafe! It is the caller's responsibility to make + /// sure that it is safe to access x [xOffset:xOffset+xLen], + /// y [yOffset:yOffset+yLen], and d [dOffset:dOffset+mod]. + /// + public static unsafe void MultiplyMod2p32pmod(uint[] x, int xOffset, int xLen, uint[] y, int yOffest, int yLen, uint[] d, int dOffset, int mod) + { + fixed (uint* xx = x, yy = y, dd = d) + { + uint* xP = xx + xOffset, + xE = xP + xLen, + yB = yy + yOffest, + yE = yB + yLen, + dB = dd + dOffset, + dE = dB + mod; + + for (; xP < xE; xP++, dB++) + { + + if (*xP == 0) continue; + + ulong mcarry = 0; + uint* dP = dB; + for (uint* yP = yB; yP < yE && dP < dE; yP++, dP++) + { + mcarry += ((ulong)*xP * (ulong)*yP) + (ulong)*dP; + + *dP = (uint)mcarry; + mcarry >>= 32; + } + + if (mcarry != 0 && dP < dE) + *dP = (uint)mcarry; + } + } + } + + public static unsafe void SquarePositive(BigInteger bi, ref uint[] wkSpace) + { + uint[] t = wkSpace; + wkSpace = bi.data; + uint[] d = bi.data; + uint dl = bi.length; + bi.data = t; + + fixed (uint* dd = d, tt = t) + { + + uint* ttE = tt + t.Length; + // Clear the dest + for (uint* ttt = tt; ttt < ttE; ttt++) + *ttt = 0; + + uint* dP = dd, tP = tt; + + for (uint i = 0; i < dl; i++, dP++) + { + if (*dP == 0) + continue; + + ulong mcarry = 0; + uint bi1val = *dP; + + uint* dP2 = dP + 1, tP2 = tP + 2 * i + 1; + + for (uint j = i + 1; j < dl; j++, tP2++, dP2++) + { + // k = i + j + mcarry += ((ulong)bi1val * (ulong)*dP2) + *tP2; + + *tP2 = (uint)mcarry; + mcarry >>= 32; + } + + if (mcarry != 0) + *tP2 = (uint)mcarry; + } + + // Double t. Inlined for speed. + + tP = tt; + + uint x, carry = 0; + while (tP < ttE) + { + x = *tP; + *tP = (x << 1) | carry; + carry = x >> (32 - 1); + tP++; + } + if (carry != 0) *tP = carry; + + // Add in the diagnals + + dP = dd; + tP = tt; + for (uint* dE = dP + dl; (dP < dE); dP++, tP++) + { + ulong val = (ulong)*dP * (ulong)*dP + *tP; + *tP = (uint)val; + val >>= 32; + *(++tP) += (uint)val; + if (*tP < (uint)val) + { + uint* tP3 = tP; + // Account for the first carry + (*++tP3)++; + + // Keep adding until no carry + while ((*tP3++) == 0) + (*tP3)++; + } + + } + + bi.length <<= 1; + + // Normalize length + while (tt[bi.length - 1] == 0 && bi.length > 1) bi.length--; + + } + } + + /* + * Never called in BigInteger (and part of a private class) + * public static bool Double (uint [] u, int l) + { + uint x, carry = 0; + uint i = 0; + while (i < l) { + x = u [i]; + u [i] = (x << 1) | carry; + carry = x >> (32 - 1); + i++; + } + if (carry != 0) u [l] = carry; + return carry != 0; + }*/ + + public static BigInteger gcd(BigInteger a, BigInteger b) + { + BigInteger x = a; + BigInteger y = b; + + BigInteger g = y; + + while (x.length > 1) + { + g = x; + x = y % x; + y = g; + + } + if (x == 0) return g; + + // TODO: should we have something here if we can convert to long? + + // + // Now we can just do it with single precision. I am using the binary gcd method, + // as it should be faster. + // + + uint yy = x.data[0]; + uint xx = y % yy; + + int t = 0; + + while (((xx | yy) & 1) == 0) + { + xx >>= 1; yy >>= 1; t++; + } + while (xx != 0) + { + while ((xx & 1) == 0) xx >>= 1; + while ((yy & 1) == 0) yy >>= 1; + if (xx >= yy) + xx = (xx - yy) >> 1; + else + yy = (yy - xx) >> 1; + } + + return yy << t; + } + + public static uint modInverse(BigInteger bi, uint modulus) + { + uint a = modulus, b = bi % modulus; + uint p0 = 0, p1 = 1; + + while (b != 0) + { + if (b == 1) + return p1; + p0 += (a / b) * p1; + a %= b; + + if (a == 0) + break; + if (a == 1) + return modulus - p0; + + p1 += (b / a) * p0; + b %= a; + + } + return 0; + } + + public static BigInteger modInverse(BigInteger bi, BigInteger modulus) + { + if (modulus.length == 1) return modInverse(bi, modulus.data[0]); + + BigInteger[] p = { 0, 1 }; + BigInteger[] q = new BigInteger[2]; // quotients + BigInteger[] r = { 0, 0 }; // remainders + + int step = 0; + + BigInteger a = modulus; + BigInteger b = bi; + + ModulusRing mr = new ModulusRing(modulus); + + while (b != 0) + { + + if (step > 1) + { + + BigInteger pval = mr.Difference(p[0], p[1] * q[0]); + p[0] = p[1]; p[1] = pval; + } + + BigInteger[] divret = multiByteDivide(a, b); + + q[0] = q[1]; q[1] = divret[0]; + r[0] = r[1]; r[1] = divret[1]; + a = b; + b = divret[1]; + + step++; + } + + if (r[0] != 1) + throw (new ArithmeticException("No inverse!")); + + return mr.Difference(p[0], p[1] * q[0]); + } + } + } +} diff --git a/Lidgren.Network/NetBitVector.cs b/Lidgren.Network/NetBitVector.cs new file mode 100644 index 0000000..4deb60a --- /dev/null +++ b/Lidgren.Network/NetBitVector.cs @@ -0,0 +1,73 @@ +using System; + +namespace Lidgren.Network +{ + public sealed class NetBitVector + { + private int m_capacity; + private uint[] m_data; + + public int Capacity { get { return m_capacity; } } + + public NetBitVector(int bitsCapacity) + { + m_capacity = bitsCapacity; + m_data = new uint[(bitsCapacity + 31) / 32]; + } + + public bool IsEmpty() + { + foreach (uint v in m_data) + if (v != 0) + return false; + return true; + } + + public int GetFirstSetIndex() + { + int idx = 0; + + uint data = m_data[0]; + while (data == 0) + { + idx++; + data = m_data[idx]; + } + + int a = 0; + while (((data >> a) & 1) == 0) + a++; + + return (idx * 32) + a; + } + + public bool Get(int bitIndex) + { + int idx = bitIndex / 32; + uint data = m_data[idx]; + int bitNr = bitIndex - (idx * 32); + return (data & (1 << bitNr)) != 0; + } + + public void Set(int bitIndex, bool value) + { + int idx = bitIndex / 32; + int bitNr = bitIndex - (idx * 32); + if (value) + m_data[idx] |= (uint)(1 << bitNr); + else + m_data[idx] &= (uint)(~(1 << bitNr)); + } + + public bool this [int index] + { + get { return Get(index); } + set { Set(index, value); } + } + + public void Clear() + { + Array.Clear(m_data, 0, m_data.Length); + } + } +} diff --git a/Lidgren.Network/NetBitWriter.cs b/Lidgren.Network/NetBitWriter.cs new file mode 100644 index 0000000..2209b33 --- /dev/null +++ b/Lidgren.Network/NetBitWriter.cs @@ -0,0 +1,432 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; + +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Helper class for NetBuffer to write/read bits + /// + public static class NetBitWriter + { + /// + /// Read 1-8 bits from a buffer into a byte + /// + public static byte ReadByte(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits < 9)), "Read() can only read between 1 and 8 bits"); + + int bytePtr = readBitOffset >> 3; + int startReadAtIndex = readBitOffset - (bytePtr * 8); // (readBitOffset % 8); + + if (startReadAtIndex == 0 && numberOfBits == 8) + return fromBuffer[bytePtr]; + + // mask away unused bits lower than (right of) relevant bits in first byte + byte returnValue = (byte)(fromBuffer[bytePtr] >> startReadAtIndex); + + int numberOfBitsInSecondByte = numberOfBits - (8 - startReadAtIndex); + + if (numberOfBitsInSecondByte < 1) + { + // we don't need to read from the second byte, but we DO need + // to mask away unused bits higher than (left of) relevant bits + return (byte)(returnValue & (255 >> (8 - numberOfBits))); + } + + byte second = fromBuffer[bytePtr + 1]; + + // mask away unused bits higher than (left of) relevant bits in second byte + second &= (byte)(255 >> (8 - numberOfBitsInSecondByte)); + + return (byte)(returnValue | (byte)(second << (numberOfBits - numberOfBitsInSecondByte))); + } + + /// + /// Read several bytes from a buffer + /// + public static void ReadBytes(byte[] fromBuffer, int numberOfBytes, int readBitOffset, byte[] destination, int destinationByteOffset) + { + int readPtr = readBitOffset >> 3; + int startReadAtIndex = readBitOffset - (readPtr * 8); // (readBitOffset % 8); + + if (startReadAtIndex == 0) + { + for (int i = 0; i < numberOfBytes; i++) + destination[destinationByteOffset++] = fromBuffer[readPtr++]; + return; + } + + int secondPartLen = 8 - startReadAtIndex; + int secondMask = 255 >> secondPartLen; + + for (int i = 0; i < numberOfBytes; i++) + { + // mask away unused bits lower than (right of) relevant bits in byte + int b = fromBuffer[readPtr] >> startReadAtIndex; + + readPtr++; + + // mask away unused bits higher than (left of) relevant bits in second byte + int second = fromBuffer[readPtr] & secondMask; + + destination[destinationByteOffset++] = (byte)(b | (second << secondPartLen)); + } + + return; + } + + /// + /// Write a byte consisting of 1-8 bits to a buffer; assumes buffer is previously allocated + /// + public static void WriteByte(byte source, int numberOfBits, byte[] destination, int destBitOffset) + { + NetException.Assert(((numberOfBits >= 1) && (numberOfBits <= 8)), "Must write between 1 and 8 bits!"); + + // mask out unwanted bits in the source + byte isrc = (byte)((uint)source & ((~(uint)0) >> (8 - numberOfBits))); + + int bytePtr = destBitOffset >> 3; + + int localBitLen = (destBitOffset % 8); + if (localBitLen == 0) + { + destination[bytePtr] = (byte)isrc; + return; + } + + //destination[bytePtr] &= (byte)(255 >> (8 - localBitLen)); // clear before writing + //destination[bytePtr] |= (byte)(isrc << localBitLen); // write first half + destination[bytePtr] = (byte)( + (uint)(destination[bytePtr] & (255 >> (8 - localBitLen))) | + (uint)(isrc << localBitLen) + ); + + // need write into next byte? + if (localBitLen + numberOfBits > 8) + { + //destination[bytePtr + 1] &= (byte)(255 << localBitLen); // clear before writing + //destination[bytePtr + 1] |= (byte)(isrc >> (8 - localBitLen)); // write second half + destination[bytePtr + 1] = (byte)( + (uint)(destination[bytePtr + 1] & (255 << localBitLen)) | + (uint)(isrc >> (8 - localBitLen)) + ); + } + + return; + } + + /// + /// Write several whole bytes + /// + public static void WriteBytes(byte[] source, int sourceByteOffset, int numberOfBytes, byte[] destination, int destBitOffset) + { + int dstBytePtr = destBitOffset >> 3; + int firstPartLen = (destBitOffset % 8); + + if (firstPartLen == 0) + { + // optimized; TODO: write 32 bit chunks if possible + for (int i = 0; i < numberOfBytes; i++) + destination[dstBytePtr++] = source[sourceByteOffset + i]; + return; + } + + int lastPartLen = 8 - firstPartLen; + + for (int i = 0; i < numberOfBytes; i++) + { + byte src = source[sourceByteOffset + i]; + + // write last part of this byte + destination[dstBytePtr] &= (byte)(255 >> lastPartLen); // clear before writing + destination[dstBytePtr] |= (byte)(src << firstPartLen); // write first half + + dstBytePtr++; + + // write first part of next byte + destination[dstBytePtr] &= (byte)(255 << firstPartLen); // clear before writing + destination[dstBytePtr] |= (byte)(src >> lastPartLen); // write second half + } + + return; + } + + [CLSCompliant(false)] +#if UNSAFE + public static unsafe uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits"); + + if (numberOfBits == 32 && ((readBitOffset % 8) == 0)) + { + fixed (byte* ptr = &(fromBuffer[readBitOffset / 8])) + { + return *(((uint*)ptr)); + } + } +#else + public static uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits"); +#endif + uint returnValue; + if (numberOfBits <= 8) + { + returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset); + return returnValue; + } + returnValue = ReadByte(fromBuffer, 8, readBitOffset); + numberOfBits -= 8; + readBitOffset += 8; + + if (numberOfBits <= 8) + { + returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8); + return returnValue; + } + returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 8); + numberOfBits -= 8; + readBitOffset += 8; + + if (numberOfBits <= 8) + { + uint r = ReadByte(fromBuffer, numberOfBits, readBitOffset); + r <<= 16; + returnValue |= r; + return returnValue; + } + returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 16); + numberOfBits -= 8; + readBitOffset += 8; + + returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 24); + +#if BIGENDIAN + // reorder bytes + return + ((a & 0xff000000) >> 24) | + ((a & 0x00ff0000) >> 8) | + ((a & 0x0000ff00) << 8) | + ((a & 0x000000ff) << 24); +#endif + + return returnValue; + } + + [CLSCompliant(false)] + public static ulong ReadUInt64(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + throw new NotImplementedException("ReadUInt64 not implemented yet"); + +#if BIGENDIAN + // reorder bytes + return ((a & 0xff00000000000000L) >> 56) | + ((a & 0x00ff000000000000L) >> 40) | + ((a & 0x0000ff0000000000L) >> 24) | + ((a & 0x000000ff00000000L) >> 8) | + ((a & 0x00000000ff000000L) << 8) | + ((a & 0x0000000000ff0000L) << 24) | + ((a & 0x000000000000ff00L) << 40) | + ((a & 0x00000000000000ffL) << 56); +#endif + } + + [CLSCompliant(false)] + public static int WriteUInt32(uint source, int numberOfBits, byte[] destination, int destinationBitOffset) + { +#if BIGENDIAN + // reorder bytes + source = ((source & 0xff000000) >> 24) | + ((source & 0x00ff0000) >> 8) | + ((source & 0x0000ff00) << 8) | + ((source & 0x000000ff) << 24); +#endif + + int returnValue = destinationBitOffset + numberOfBits; + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + + [CLSCompliant(false)] + public static int WriteUInt64(ulong source, int numberOfBits, byte[] destination, int destinationBitOffset) + { +#if BIGENDIAN + source = ((source & 0xff00000000000000L) >> 56) | + ((source & 0x00ff000000000000L) >> 40) | + ((source & 0x0000ff0000000000L) >> 24) | + ((source & 0x000000ff00000000L) >> 8) | + ((source & 0x00000000ff000000L) << 8) | + ((source & 0x0000000000ff0000L) << 24) | + ((source & 0x000000000000ff00L) << 40) | + ((source & 0x00000000000000ffL) << 56); +#endif + + int returnValue = destinationBitOffset + numberOfBits; + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 24), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 32), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 32), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 40), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 40), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 48), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 48), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 56), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 56), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + return returnValue; + } + + // + // Variable size + // + + /// + /// Write Base128 encoded variable sized unsigned integer + /// + /// number of bytes written + [CLSCompliant(false)] + public static int WriteVariableUInt32(byte[] intoBuffer, int offset, uint value) + { + int retval = 0; + uint num1 = (uint)value; + while (num1 >= 0x80) + { + intoBuffer[offset + retval] = (byte)(num1 | 0x80); + num1 = num1 >> 7; + retval++; + } + intoBuffer[offset + retval] = (byte)num1; + return retval + 1; + } + + /// + /// Reads a UInt32 written using WriteUnsignedVarInt(); will increment offset! + /// + [CLSCompliant(false)] + public static uint ReadVariableUInt32(byte[] buffer, ref int offset) + { + int num1 = 0; + int num2 = 0; + while (true) + { + if (num2 == 0x23) + throw new FormatException("Bad 7-bit encoded integer"); + + byte num3 = buffer[offset++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + return (uint)num1; + } + } + } +} diff --git a/Lidgren.Network/NetClient.cs b/Lidgren.Network/NetClient.cs new file mode 100644 index 0000000..097f3ce --- /dev/null +++ b/Lidgren.Network/NetClient.cs @@ -0,0 +1,104 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + public class NetClient : NetPeer + { + /// + /// Gets the connection to the server, if any + /// + public NetConnection ServerConnection + { + get + { + NetConnection retval = null; + if (m_connections.Count > 0) + { + try + { + retval = m_connections[0]; + } + catch + { + // preempted! + return null; + } + } + return retval; + } + } + + public NetClient(NetPeerConfiguration config) + : base(config) + { + config.AcceptIncomingConnections = false; + } + + /// + /// Disconnect from server + /// + /// reason for disconnect + public void Disconnect(string byeMessage) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + LogWarning("Disconnect requested when not connected!"); + return; + } + serverConnection.Disconnect(byeMessage); + } + + /// + /// Sends message to server + /// + public void SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + //LogError("Cannot send message, no server connection!"); + return; + } + serverConnection.SendMessage(msg, method); + } + + /// + /// Sends message to server + /// + public void SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + //LogError("Cannot send message, no server connection!"); + return; + } + serverConnection.SendMessage(msg, method, sequenceChannel); + } + + public override string ToString() + { + return "[NetClient " + ServerConnection + "]"; + } + + } +} diff --git a/Lidgren.Network/NetConnection.Handshake.cs b/Lidgren.Network/NetConnection.Handshake.cs new file mode 100644 index 0000000..76038c6 --- /dev/null +++ b/Lidgren.Network/NetConnection.Handshake.cs @@ -0,0 +1,246 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + public partial class NetConnection + { + internal bool m_connectRequested; + internal string m_disconnectByeMessage; + internal bool m_connectionInitiator; + internal double m_connectInitationTime; // regardless of initiator + internal NetOutgoingMessage m_approvalMessage; + + internal void SetStatus(NetConnectionStatus status, string reason) + { + m_owner.VerifyNetworkThread(); + + if (status == m_status) + return; + m_status = status; + if (reason == null) + reason = string.Empty; + + if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.StatusChanged)) + { + NetIncomingMessage info = m_owner.CreateIncomingMessage(NetIncomingMessageType.StatusChanged, 4 + reason.Length + (reason.Length > 126 ? 2 : 1)); + info.m_senderConnection = this; + info.m_senderEndpoint = m_remoteEndpoint; + info.Write((byte)m_status); + info.Write(reason); + m_owner.ReleaseMessage(info); + } + } + + private void SendConnect() + { + m_owner.VerifyNetworkThread(); + + switch (m_status) + { + case NetConnectionStatus.Connected: + // reconnect + m_disconnectByeMessage = "Reconnecting"; + ExecuteDisconnect(true); + FinishDisconnect(); + break; + case NetConnectionStatus.Connecting: + case NetConnectionStatus.None: + break; + case NetConnectionStatus.Disconnected: + throw new NetException("This connection is Disconnected; spent. A new one should have been created"); + + case NetConnectionStatus.Disconnecting: + // let disconnect finish first + return; + } + + m_connectRequested = false; + + // start handshake + + int len = 2 + m_peerConfiguration.AppIdentifier.Length + 8 + 4 + (m_approvalMessage == null ? 0 : m_approvalMessage.LengthBytes); + NetOutgoingMessage om = m_owner.CreateMessage(len); + om.m_type = NetMessageType.Library; + om.m_libType = NetMessageLibraryType.Connect; + om.Write(m_peerConfiguration.AppIdentifier); + om.Write(m_owner.m_uniqueIdentifier); + + if (m_approvalMessage == null) + { + om.WriteVariableUInt32(0); + } + else + { + om.WriteVariableUInt32((uint)m_approvalMessage.LengthBits); + om.Write(m_approvalMessage); + } + m_owner.LogVerbose("Sending Connect"); + + m_owner.SendImmediately(this, om); + + m_connectInitationTime = NetTime.Now; + SetStatus(NetConnectionStatus.Connecting, "Connecting"); + + return; + } + + internal void SendConnectResponse() + { + NetOutgoingMessage reply = m_owner.CreateMessage(4); + reply.m_type = NetMessageType.Library; + reply.m_libType = NetMessageLibraryType.ConnectResponse; + reply.Write((float)NetTime.Now); + + m_owner.LogVerbose("Sending LibraryConnectResponse"); + m_owner.SendImmediately(this, reply); + } + + internal void SendConnectionEstablished() + { + NetOutgoingMessage ce = m_owner.CreateMessage(4); + ce.m_type = NetMessageType.Library; + ce.m_libType = NetMessageLibraryType.ConnectionEstablished; + ce.Write((float)NetTime.Now); + + m_owner.LogVerbose("Sending LibraryConnectionEstablished"); + m_owner.SendImmediately(this, ce); + } + + internal void ExecuteDisconnect(bool sendByeMessage) + { + m_owner.VerifyNetworkThread(); + + if (m_status == NetConnectionStatus.Disconnected || m_status == NetConnectionStatus.None) + return; + + if (sendByeMessage) + { + NetOutgoingMessage om = m_owner.CreateLibraryMessage(NetMessageLibraryType.Disconnect, m_disconnectByeMessage); + EnqueueOutgoingMessage(om); + } + + m_owner.LogVerbose("Executing Disconnect(" + m_disconnectByeMessage + ")"); + + return; + } + + private void FinishDisconnect() + { + m_owner.VerifyNetworkThread(); + + if (m_status == NetConnectionStatus.Disconnected || m_status == NetConnectionStatus.None) + return; + + m_owner.LogVerbose("Finishing Disconnect(" + m_disconnectByeMessage + ")"); + + // release some held memory + if (m_storedMessages != null) + { + foreach (List oml in m_storedMessages) + if (oml != null) + oml.Clear(); + } + m_acknowledgesToSend.Clear(); + + SetStatus(NetConnectionStatus.Disconnected, m_disconnectByeMessage); + m_disconnectByeMessage = null; + m_connectionInitiator = false; + } + + private void HandleIncomingHandshake(NetMessageLibraryType ltp, int ptr, int payloadBitsLength) + { + m_owner.VerifyNetworkThread(); + + switch (ltp) + { + case NetMessageLibraryType.Connect: + if (m_status == NetConnectionStatus.Connecting) + { + // our connectresponse must have been lost, send another one + SendConnectResponse(); + return; + } + + m_owner.LogError("NetConnection.HandleIncomingHandshake() passed LibraryConnect but status is " + m_status + "!?"); + break; + case NetMessageLibraryType.ConnectResponse: + if (!m_connectionInitiator) + { + m_owner.LogError("NetConnection.HandleIncomingHandshake() passed LibraryConnectResponse, but we're not initiator!"); + // weird, just drop it + return; + } + + if (m_status == NetConnectionStatus.Connecting) + { + m_owner.m_statistics.m_bytesAllocated += NetUtility.BytesToHoldBits(payloadBitsLength); + + float remoteNetTime = BitConverter.ToSingle(m_owner.m_receiveBuffer, ptr); + ptr += 4; + + // excellent, handshake making progress; send connectionestablished + SetStatus(NetConnectionStatus.Connected, "Connected"); + + SendConnectionEstablished(); + + // setup initial ping estimation + InitializeLatency((float)(NetTime.Now - m_connectInitationTime), remoteNetTime); + return; + } + + if (m_status == NetConnectionStatus.Connected) + { + // received (another) connectresponse; our connectionestablished must have been lost, send another one + SendConnectionEstablished(); + return; + } + + m_owner.LogWarning("NetConnection.HandleIncomingHandshake() passed " + ltp + ", but status is " + m_status); + break; + case NetMessageLibraryType.ConnectionEstablished: + if (!m_connectionInitiator && m_status == NetConnectionStatus.Connecting) + { + float remoteNetTime = BitConverter.ToSingle(m_owner.m_receiveBuffer, ptr); + + // handshake done + InitializeLatency((float)(NetTime.Now - m_connectInitationTime), remoteNetTime); + + SetStatus(NetConnectionStatus.Connected, "Connected"); + return; + } + + m_owner.LogWarning("NetConnection.HandleIncomingHandshake() passed " + ltp + ", but initiator is " + m_connectionInitiator + " and status is " + m_status); + break; + case NetMessageLibraryType.Disconnect: + // extract bye message + NetIncomingMessage im = m_owner.CreateIncomingMessage(NetIncomingMessageType.Data, m_owner.m_receiveBuffer, ptr, NetUtility.BytesToHoldBits(payloadBitsLength)); + im.m_bitLength = payloadBitsLength; + m_disconnectByeMessage = im.ReadString(); + FinishDisconnect(); + break; + default: + // huh? + throw new NotImplementedException("Unhandled library type: " + ltp); + } + } + } +} diff --git a/Lidgren.Network/NetConnection.Latency.cs b/Lidgren.Network/NetConnection.Latency.cs new file mode 100644 index 0000000..3b8a40b --- /dev/null +++ b/Lidgren.Network/NetConnection.Latency.cs @@ -0,0 +1,156 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + public partial class NetConnection + { + // + // Connection keepalive and latency calculation + // + internal float m_averageRoundtripTime = 0.05f; + private byte m_lastSentPingNumber; + private double m_lastPingSendTime; + private double m_nextPing; + private double m_lastSendRespondedTo; + + // remote + diff = local + // diff = local - remote + // local - diff = remote + internal double m_remoteToLocalNetTime = double.MinValue; + + public float AverageRoundtripTime { get { return m_averageRoundtripTime; } } + + internal void InitializeLatency(float rtt, float remoteNetTime) + { + m_averageRoundtripTime = Math.Max(0.005f, rtt - 0.005f); // TODO: find out why initial ping always overshoots + m_nextPing = NetTime.Now + m_peerConfiguration.m_pingFrequency / 2.0f; + + double currentRemoteNetTime = remoteNetTime - (rtt * 0.5); + m_remoteToLocalNetTime = (float)(NetTime.Now - currentRemoteNetTime); + + StringBuilder bdr = new StringBuilder(); + bdr.Append("Initialized average roundtrip time to: "); + bdr.Append(NetTime.ToReadable(m_averageRoundtripTime)); + bdr.Append(" remote time diff to "); + bdr.Append(NetTime.ToReadable(m_remoteToLocalNetTime)); + m_owner.LogDebug(bdr.ToString()); + } + + internal void HandleIncomingPing(byte pingNumber) + { + // send pong + NetOutgoingMessage pong = m_owner.CreateMessage(1); + pong.m_type = NetMessageType.Library; + pong.m_libType = NetMessageLibraryType.Pong; + pong.Write((byte)pingNumber); + pong.Write(NetTime.Now); + + // send immediately + m_owner.SendImmediately(this, pong); + } + + internal void HandleIncomingPong(double receiveNow, byte pingNumber, double remoteNetTime) + { + // verify it´s the correct ping number + if (pingNumber != m_lastSentPingNumber) + { + m_owner.LogError("Received wrong pong number; got " + pingNumber + " expected " + m_lastSentPingNumber); + + // but still, a pong must have been triggered by a ping... + double diff = receiveNow - m_lastSendRespondedTo; + if (diff > 0) + m_lastSendRespondedTo += (diff / 2); // postpone timing out a bit + + return; + } + + double now = NetTime.Now; // need exact number + + m_lastHeardFrom = now; + m_lastSendRespondedTo = m_lastPingSendTime; + + double rtt = now - m_lastPingSendTime; + + // update clock sync + double currentRemoteNetTime = remoteNetTime - (rtt * 0.5); + double foundDiffRemoteToLocal = receiveNow - currentRemoteNetTime; + + if (m_remoteToLocalNetTime == double.MinValue) + m_remoteToLocalNetTime = foundDiffRemoteToLocal; + else + m_remoteToLocalNetTime = ((m_remoteToLocalNetTime + foundDiffRemoteToLocal) * 0.5); + + m_averageRoundtripTime = (m_averageRoundtripTime * 0.7f) + (float)(rtt * 0.3f); + m_owner.LogVerbose("Found rtt: " + NetTime.ToReadable(rtt) + ", new average: " + NetTime.ToReadable(m_averageRoundtripTime) + " new time diff: " + NetTime.ToReadable(m_remoteToLocalNetTime)); + } + + internal void KeepAliveHeartbeat(double now) + { + // do keepalive and latency pings + if (m_status == NetConnectionStatus.Disconnected || m_status == NetConnectionStatus.None) + return; + + // in case of inactivity; send forced keepalive/ping + if (now > m_lastSendRespondedTo + m_peerConfiguration.m_keepAliveDelay) + m_nextPing = now; + + // force ack sending? + if (now > m_nextForceAckTime) + { + // send keepalive thru regular channels to give acks something to piggyback on + NetOutgoingMessage pig = m_owner.CreateMessage(1); + pig.m_type = NetMessageType.Library; + pig.m_libType = NetMessageLibraryType.KeepAlive; + EnqueueOutgoingMessage(pig); + m_nextForceAckTime = now + m_peerConfiguration.m_maxAckDelayTime; + } + + // timeout + if (now > m_lastSendRespondedTo + m_peerConfiguration.m_connectionTimeout) + { + Disconnect("Timed out after " + (now - m_lastSendRespondedTo) + " seconds"); + return; + } + + // ping time? + if (now >= m_nextPing) + { + // + // send ping + // + m_lastSentPingNumber++; + + NetOutgoingMessage ping = m_owner.CreateMessage(1); + ping.m_type = NetMessageType.Library; + ping.m_libType = NetMessageLibraryType.Ping; + ping.Write((byte)m_lastSentPingNumber); + + m_owner.SendImmediately(this, ping); + + m_lastPingSendTime = NetTime.Now; // need exact number + m_nextPing = now + m_owner.Configuration.m_pingFrequency; + } + } + } +} diff --git a/Lidgren.Network/NetConnection.Reliability.cs b/Lidgren.Network/NetConnection.Reliability.cs new file mode 100644 index 0000000..4a57867 --- /dev/null +++ b/Lidgren.Network/NetConnection.Reliability.cs @@ -0,0 +1,266 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Lidgren.Network +{ + public partial class NetConnection + { + private ushort[] m_nextSendSequenceNumber; + private ushort[] m_lastReceivedSequenced; + + internal List[] m_storedMessages; // naïve! replace by something better? + internal NetBitVector m_storedMessagesNotEmpty; + + private ushort[] m_nextExpectedReliableSequence; + private List[] m_withheldMessages; + internal Queue m_acknowledgesToSend; + internal double m_nextForceAckTime; + + private NetBitVector[] m_reliableReceived; + + public int GetStoredMessagesCount() + { + int retval = 0; + for (int i = 0; i < m_storedMessages.Length; i++) + { + List list = m_storedMessages[i]; + if (list != null) + retval += list.Count; + } + return retval; + } + + private void InitializeReliability() + { + int num = ((int)NetMessageType.UserReliableOrdered + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced; + m_nextSendSequenceNumber = new ushort[num]; + m_lastReceivedSequenced = new ushort[num]; + m_nextForceAckTime = double.MaxValue; + + m_storedMessages = new List[NetConstants.NumReliableChannels]; + m_storedMessagesNotEmpty = new NetBitVector(NetConstants.NumReliableChannels); + + m_reliableReceived = new NetBitVector[NetConstants.NumSequenceNumbers]; + m_nextExpectedReliableSequence = new ushort[NetConstants.NumReliableChannels]; + m_withheldMessages = new List[NetConstants.NetChannelsPerDeliveryMethod]; // only for ReliableOrdered + m_acknowledgesToSend = new Queue(); + } + + internal ushort GetSendSequenceNumber(NetMessageType mtp) + { + m_owner.VerifyNetworkThread(); + int slot = (int)mtp - (int)NetMessageType.UserSequenced; + return m_nextSendSequenceNumber[slot]++; + } + + internal static int Relate(int seqNr, int lastReceived) + { + return (seqNr < lastReceived ? (seqNr + NetConstants.NumSequenceNumbers) - lastReceived : seqNr - lastReceived); + } + + // returns true if message should be rejected + internal bool ReceivedSequencedMessage(NetMessageType mtp, ushort seqNr) + { + int slot = (int)mtp - (int)NetMessageType.UserSequenced; + + int diff = Relate(seqNr, m_lastReceivedSequenced[slot]); + + if (diff == 0) + return true; // reject; already received + if (diff > (ushort.MaxValue / 2)) + return true; // reject; out of window + + m_lastReceivedSequenced[slot] = seqNr; + return false; + } + + // called the FIRST time a reliable message is sent + private void StoreReliableMessage(double now, NetOutgoingMessage msg) + { + m_owner.VerifyNetworkThread(); + + int reliableSlot = (int)msg.m_type - (int)NetMessageType.UserReliableUnordered; + + List list = m_storedMessages[reliableSlot]; + if (list == null) + { + list = new List(); + m_storedMessages[reliableSlot] = list; + } + Interlocked.Increment(ref msg.m_inQueueCount); + list.Add(msg); + + if (list.Count == 1) + m_storedMessagesNotEmpty.Set(reliableSlot, true); + + // schedule next resend + int numSends = msg.m_numSends; + float[] baseTimes = m_peerConfiguration.m_resendBaseTime; + float[] multiplers = m_peerConfiguration.m_resendRTTMultiplier; + msg.m_nextResendTime = now + baseTimes[numSends] + (m_averageRoundtripTime * multiplers[numSends]); + } + + private void Resend(double now, NetOutgoingMessage msg) + { + m_owner.VerifyNetworkThread(); + + int numSends = msg.m_numSends; + float[] baseTimes = m_peerConfiguration.m_resendBaseTime; + if (numSends >= baseTimes.Length) + { + // no more resends! We failed! + int reliableSlot = (int)msg.m_type - (int)NetMessageType.UserReliableUnordered; + List list = m_storedMessages[reliableSlot]; + list.Remove(msg); + m_owner.LogWarning("Failed to deliver reliable message " + msg); + + Disconnect("Failed to deliver reliable message!"); + + return; // bye + } + + m_owner.LogVerbose("Resending " + msg); + + Interlocked.Increment(ref msg.m_inQueueCount); + m_unsentMessages.EnqueueFirst(msg); + + msg.m_lastSentTime = now; + + // schedule next resend + float[] multiplers = m_peerConfiguration.m_resendRTTMultiplier; + msg.m_nextResendTime = now + baseTimes[numSends] + (m_averageRoundtripTime * multiplers[numSends]); + } + + private void HandleIncomingAcks(int ptr, int payloadByteLength) + { + m_owner.VerifyNetworkThread(); + + int numAcks = payloadByteLength / 3; + if (numAcks * 3 != payloadByteLength) + m_owner.LogWarning("Malformed ack message; payload length is " + payloadByteLength); + + byte[] buffer = m_owner.m_receiveBuffer; + for (int i = 0; i < numAcks; i++) + { + ushort seqNr = (ushort)(buffer[ptr++] | (buffer[ptr++] << 8)); + NetMessageType tp = (NetMessageType)buffer[ptr++]; + // m_owner.LogDebug("Got ack for " + tp + " " + seqNr); + + // remove stored message + int reliableSlot = (int)tp - (int)NetMessageType.UserReliableUnordered; + + List list = m_storedMessages[reliableSlot]; + if (list == null) + continue; + + // find message + for (int a = 0; a < list.Count; a++) + { + NetOutgoingMessage om = list[a]; + if (om.m_sequenceNumber == seqNr) + { + // found! + list.RemoveAt(a); + Interlocked.Decrement(ref om.m_inQueueCount); + + NetException.Assert(om.m_lastSentTime != 0); + + if (om.m_lastSentTime > m_lastSendRespondedTo) + m_lastSendRespondedTo = om.m_lastSentTime; + + if (om.m_inQueueCount < 1) + m_owner.Recycle(om); + + break; + } + } + + // TODO: receipt handling + } + } + + private void ExpectedReliableSequenceArrived(int reliableSlot) + { + NetBitVector received = m_reliableReceived[reliableSlot]; + + int nextExpected = m_nextExpectedReliableSequence[reliableSlot]; + + if (received == null) + { + nextExpected = (nextExpected + 1) % NetConstants.NumSequenceNumbers; + m_nextExpectedReliableSequence[reliableSlot] = (ushort)nextExpected; + return; + } + + received[(nextExpected + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers] = false; // reset for next pass + nextExpected = (nextExpected + 1) % NetConstants.NumSequenceNumbers; + + while (received[nextExpected] == true) + { + // it seems we've already received the next expected reliable sequence number + + // ordered? + const int orderedSlotsStart = ((int)NetMessageType.UserReliableOrdered - (int)NetMessageType.UserReliableUnordered); + if (reliableSlot >= orderedSlotsStart) + { + // ... then we should have a withheld message waiting + + // this should be a withheld message + int orderedSlot = reliableSlot - orderedSlotsStart; + bool foundWithheld = false; + + List withheldList = m_withheldMessages[orderedSlot]; + if (withheldList != null) + { + foreach (NetIncomingMessage wm in withheldList) + { + int wmSeqChan = wm.SequenceChannel; + + if (orderedSlot == wmSeqChan && wm.m_sequenceNumber == nextExpected) + { + // Found withheld message due for delivery + m_owner.LogVerbose("Releasing withheld message " + wm); + + // AcceptMessage + m_owner.ReleaseMessage(wm); + + foundWithheld = true; + withheldList.Remove(wm); + + // advance next expected + received[(nextExpected + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers] = false; // reset for next pass + nextExpected = (nextExpected + 1) % NetConstants.NumSequenceNumbers; + + break; + } + } + } + if (!foundWithheld) + throw new NetException("Failed to find withheld message!"); + } + } + + m_nextExpectedReliableSequence[reliableSlot] = (ushort)nextExpected; + } + } +} diff --git a/Lidgren.Network/NetConnection.cs b/Lidgren.Network/NetConnection.cs new file mode 100644 index 0000000..0d7494f --- /dev/null +++ b/Lidgren.Network/NetConnection.cs @@ -0,0 +1,652 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Threading; + +namespace Lidgren.Network +{ + [DebuggerDisplay("RemoteEndpoint={m_remoteEndpoint} Status={m_status}")] + public partial class NetConnection + { + private NetPeer m_owner; + internal IPEndPoint m_remoteEndpoint; + internal double m_lastHeardFrom; + internal NetQueue m_unsentMessages; + internal NetConnectionStatus m_status; + private double m_lastSentUnsentMessages; + private float m_throttleDebt; + private NetPeerConfiguration m_peerConfiguration; + internal NetConnectionStatistics m_statistics; + private int m_lesserHeartbeats; + private int m_nextFragmentGroupId; + internal long m_remoteUniqueIdentifier; + private Dictionary m_fragmentGroups; + private int m_handshakeAttempts; + + internal PendingConnectionStatus m_pendingStatus = PendingConnectionStatus.NotPending; + internal string m_pendingDenialReason; + + /// + /// Gets or sets the object containing data about the connection + /// + public object Tag { get; set; } + + /// + /// Statistics for the connection + /// + public NetConnectionStatistics Statistics { get { return m_statistics; } } + + /// + /// The unique identifier of the remote NetPeer for this connection + /// + public long RemoteUniqueIdentifier { get { return m_remoteUniqueIdentifier; } } + + /// + /// Gets the remote endpoint for the connection + /// + public IPEndPoint RemoteEndpoint { get { return m_remoteEndpoint; } } + + internal NetConnection(NetPeer owner, IPEndPoint remoteEndpoint) + { + m_owner = owner; + m_peerConfiguration = m_owner.m_configuration; + m_remoteEndpoint = remoteEndpoint; + m_unsentMessages = new NetQueue(16); + m_fragmentGroups = new Dictionary(); + m_status = NetConnectionStatus.None; + + double now = NetTime.Now; + m_nextPing = now + 5.0f; + m_nextForceAckTime = double.MaxValue; + m_lastSentUnsentMessages = now; + m_lastSendRespondedTo = now; + m_statistics = new NetConnectionStatistics(this); + + InitializeReliability(); + } + + // run on network thread + internal void Heartbeat(double now) + { + m_owner.VerifyNetworkThread(); + + m_lesserHeartbeats++; + + if (m_lesserHeartbeats >= 2) + { + // + // Do greater heartbeat every third heartbeat + // + m_lesserHeartbeats = 0; + + // keepalive, timeout and ping stuff + KeepAliveHeartbeat(now); + + if (m_connectRequested) + SendConnect(); + + if (m_status == NetConnectionStatus.Connecting && now - m_connectInitationTime > m_owner.m_configuration.m_handshakeAttemptDelay) + { + if (m_connectionInitiator) + SendConnect(); + else + SendConnectResponse(); + + m_connectInitationTime = now; + + if (++m_handshakeAttempts >= m_owner.m_configuration.m_handshakeMaxAttempts) + { + Disconnect("Failed to complete handshake"); + return; + } + } + + // queue resends + if (!m_storedMessagesNotEmpty.IsEmpty()) + { + int first = m_storedMessagesNotEmpty.GetFirstSetIndex(); + for (int i = first; i < m_storedMessages.Length; i++) + { + if (m_storedMessagesNotEmpty.Get(i)) + { + foreach (NetOutgoingMessage om in m_storedMessages[i]) + { + if (now >= om.m_nextResendTime) + { + Resend(now, om); + break; // need to break out here; collection may have been modified + } + } + } +#if DEBUG + else + { + NetException.Assert(m_storedMessages[i] == null || m_storedMessages[i].Count < 1, "m_storedMessagesNotEmpty fail!"); + } +#endif + } + } + } + + // send unsent messages; high priority first + byte[] buffer = m_owner.m_sendBuffer; + int ptr = 0; + + float throttle = m_peerConfiguration.m_throttleBytesPerSecond; + if (throttle > 0) + { + double frameLength = now - m_lastSentUnsentMessages; + if (m_throttleDebt > 0) + m_throttleDebt -= (float)(frameLength * throttle); + m_lastSentUnsentMessages = now; + } + + int mtu = m_peerConfiguration.MaximumTransmissionUnit; + + float throttleThreshold = m_peerConfiguration.m_throttlePeakBytes; + if (m_throttleDebt < throttleThreshold) + { + // + // Send new unsent messages + // + int numIncludedMessages = 0; + while (m_unsentMessages.Count > 0) + { + if (m_throttleDebt >= throttleThreshold) + break; + + NetOutgoingMessage msg = m_unsentMessages.TryDequeue(); + if (msg == null) + continue; + Interlocked.Decrement(ref msg.m_inQueueCount); + + int msgPayloadLength = msg.LengthBytes; + msg.m_lastSentTime = now; + + if (ptr > 0 && (ptr + NetPeer.kMaxPacketHeaderSize + msgPayloadLength) > mtu) + { + // send packet and start new packet + m_owner.SendPacket(ptr, m_remoteEndpoint, numIncludedMessages); + m_statistics.PacketSent(ptr, numIncludedMessages); + numIncludedMessages = 0; + m_throttleDebt += ptr; + ptr = 0; + } + + // + // encode message + // + + ptr = msg.Encode(buffer, ptr, this); + numIncludedMessages++; + + if (msg.m_type >= NetMessageType.UserReliableUnordered && msg.m_numSends == 1) + { + // message is sent for the first time, and is reliable, store for resend + StoreReliableMessage(now, msg); + } + + // room to piggyback some acks? + if (m_acknowledgesToSend.Count > 0) + { + int payloadLeft = (mtu - ptr) - NetPeer.kMaxPacketHeaderSize; + if (payloadLeft > 9) + { + // yes, add them as a regular message + ptr = NetOutgoingMessage.EncodeAcksMessage(m_owner.m_sendBuffer, ptr, this, (payloadLeft - 3)); + + if (m_acknowledgesToSend.Count < 1) + m_nextForceAckTime = double.MaxValue; + } + } + + if (msg.m_type == NetMessageType.Library && msg.m_libType == NetMessageLibraryType.Disconnect) + { + FinishDisconnect(); + break; + } + + if (msg.m_inQueueCount < 1) + m_owner.Recycle(msg); + } + + if (ptr > 0) + { + m_owner.SendPacket(ptr, m_remoteEndpoint, numIncludedMessages); + m_statistics.PacketSent(ptr, numIncludedMessages); + numIncludedMessages = 0; + m_throttleDebt += ptr; + } + } + } + + internal void HandleUserMessage(double now, NetMessageType mtp, bool isFragment, ushort channelSequenceNumber, int ptr, int payloadLengthBits) + { + m_owner.VerifyNetworkThread(); + + try + { + NetDeliveryMethod ndm = NetPeer.GetDeliveryMethod(mtp); + + // + // Unreliable + // + if (ndm == NetDeliveryMethod.Unreliable) + { + AcceptMessage(mtp, isFragment, channelSequenceNumber, ptr, payloadLengthBits); + return; + } + + // + // UnreliableSequenced + // + if (ndm == NetDeliveryMethod.UnreliableSequenced) + { + bool reject = ReceivedSequencedMessage(mtp, channelSequenceNumber); + if (!reject) + AcceptMessage(mtp, isFragment, channelSequenceNumber, ptr, payloadLengthBits); + return; + } + + // + // Reliable delivery methods below + // + + // queue ack + m_acknowledgesToSend.Enqueue((int)channelSequenceNumber | ((int)mtp << 16)); + if (m_nextForceAckTime == double.MaxValue) + m_nextForceAckTime = now + m_peerConfiguration.m_maxAckDelayTime; + + if (ndm == NetDeliveryMethod.ReliableSequenced) + { + bool reject = ReceivedSequencedMessage(mtp, channelSequenceNumber); + if (!reject) + AcceptMessage(mtp, isFragment, channelSequenceNumber, ptr, payloadLengthBits); + return; + } + + // relate to all received up to + int reliableSlot = (int)mtp - (int)NetMessageType.UserReliableUnordered; + int diff = Relate(channelSequenceNumber, m_nextExpectedReliableSequence[reliableSlot]); + + if (diff > (ushort.MaxValue / 2)) + { + // Reject out-of-window + //m_statistics.CountDuplicateMessage(msg); + m_owner.LogVerbose("Rejecting duplicate reliable " + mtp + " " + channelSequenceNumber); + return; + } + + if (diff == 0) + { + // Expected sequence number + AcceptMessage(mtp, isFragment, channelSequenceNumber, ptr, payloadLengthBits); + + ExpectedReliableSequenceArrived(reliableSlot); + return; + } + + // + // Early reliable message - we must check if it's already been received + // + // DeliveryMethod is ReliableUnordered or ReliableOrdered here + // + + // get bools list we must check + NetBitVector recList = m_reliableReceived[reliableSlot]; + if (recList == null) + { + recList = new NetBitVector(NetConstants.NumSequenceNumbers); + m_reliableReceived[reliableSlot] = recList; + } + + if (recList[channelSequenceNumber]) + { + // Reject duplicate + //m_statistics.CountDuplicateMessage(msg); + m_owner.LogVerbose("Rejecting duplicate reliable " + ndm.ToString() + channelSequenceNumber.ToString()); + return; + } + + // It's an early reliable message + recList[channelSequenceNumber] = true; + + m_owner.LogVerbose("Received early reliable message: " + channelSequenceNumber); + + // + // It's not a duplicate; mark as received. Release if it's unordered, else withhold + // + + if (ndm == NetDeliveryMethod.ReliableUnordered) + { + AcceptMessage(mtp, isFragment, channelSequenceNumber, ptr, payloadLengthBits); + return; + } + + // + // Only ReliableOrdered left here; withhold it + // + + // Early ordered message; withhold + const int orderedSlotsStart = ((int)NetMessageType.UserReliableOrdered - (int)NetMessageType.UserReliableUnordered); + int orderedSlot = reliableSlot - orderedSlotsStart; + + List wmList = m_withheldMessages[orderedSlot]; + if (wmList == null) + { + wmList = new List(); + m_withheldMessages[orderedSlot] = wmList; + } + + // create message + NetIncomingMessage im = m_owner.CreateIncomingMessage(NetIncomingMessageType.Data, m_owner.m_receiveBuffer, ptr, NetUtility.BytesToHoldBits(payloadLengthBits)); + im.m_bitLength = payloadLengthBits; + im.m_messageType = mtp; + im.m_sequenceNumber = channelSequenceNumber; + im.m_senderConnection = this; + im.m_senderEndpoint = m_remoteEndpoint; + + m_owner.LogVerbose("Withholding " + im + " (waiting for " + m_nextExpectedReliableSequence[reliableSlot] + ")"); + + wmList.Add(im); + + return; + } + catch (Exception ex) + { +#if DEBUG + throw new NetException("Message generated exception: " + ex, ex); +#else + m_owner.LogError("Message generated exception: " + ex); + return; +#endif + } + } + + private void AcceptMessage(NetMessageType mtp, bool isFragment, ushort seqNr, int ptr, int payloadLengthBits) + { + byte[] buffer = m_owner.m_receiveBuffer; + NetIncomingMessage im; + int bytesLen = NetUtility.BytesToHoldBits(payloadLengthBits); + + if (isFragment) + { + int fragmentGroup = buffer[ptr++] | (buffer[ptr++] << 8); + int fragmentTotalCount = buffer[ptr++] | (buffer[ptr++] << 8); + int fragmentNr = buffer[ptr++] | (buffer[ptr++] << 8); + + // do we already have fragments of this group? + if (!m_fragmentGroups.TryGetValue(fragmentGroup, out im)) + { + // new fragmented message + int estLength = fragmentTotalCount * bytesLen; + + im = m_owner.CreateIncomingMessage(NetIncomingMessageType.Data, estLength); + im.m_messageType = mtp; + im.m_sequenceNumber = seqNr; + im.m_senderConnection = this; + im.m_senderEndpoint = m_remoteEndpoint; + NetFragmentationInfo info = new NetFragmentationInfo(); + info.TotalFragmentCount = fragmentTotalCount; + info.Received = new bool[fragmentTotalCount]; + info.FragmentSize = bytesLen; + im.m_fragmentationInfo = info; + m_fragmentGroups[fragmentGroup] = im; + } + + // insert this fragment at correct position + bool done = InsertFragment(im, fragmentNr, ptr, bytesLen); + if (!done) + return; + + // all received! + m_fragmentGroups.Remove(fragmentGroup); + } + else + { + // non-fragmented - release to application + im = m_owner.CreateIncomingMessage(NetIncomingMessageType.Data, buffer, ptr, bytesLen); + im.m_bitLength = payloadLengthBits; + im.m_messageType = mtp; + im.m_sequenceNumber = seqNr; + im.m_senderConnection = this; + im.m_senderEndpoint = m_remoteEndpoint; + } + + // m_owner.LogVerbose("Releasing " + im); + m_owner.ReleaseMessage(im); + } + + private bool InsertFragment(NetIncomingMessage im, int nr, int ptr, int payloadLength) + { + NetFragmentationInfo info = im.m_fragmentationInfo; + + if (nr >= info.TotalFragmentCount) + { + m_owner.LogError("Received fragment larger than total fragments! (total " + info.TotalFragmentCount + ", nr " + nr + ")"); + return false; + } + + if (info.Received[nr] == true) + { + // duplicate fragment + return false; + } + + // insert data + int offset = nr * info.FragmentSize; + + if (im.m_data.Length < offset + payloadLength) + Array.Resize(ref im.m_data, offset + payloadLength); + + Buffer.BlockCopy(m_owner.m_receiveBuffer, ptr, im.m_data, offset, payloadLength); + + im.m_bitLength = (8 * (offset + payloadLength)); + + info.Received[nr] = true; + info.TotalReceived++; + + return info.TotalReceived >= info.TotalFragmentCount; + } + + internal void HandleLibraryMessage(double now, NetMessageLibraryType libType, int ptr, int payloadLengthBits) + { + m_owner.VerifyNetworkThread(); + + switch (libType) + { + case NetMessageLibraryType.Error: + m_owner.LogWarning("Received NetMessageLibraryType.Error message!"); + break; + case NetMessageLibraryType.Connect: + case NetMessageLibraryType.ConnectResponse: + case NetMessageLibraryType.ConnectionEstablished: + case NetMessageLibraryType.Disconnect: + HandleIncomingHandshake(libType, ptr, payloadLengthBits); + break; + case NetMessageLibraryType.KeepAlive: + // no operation, we just want the acks + break; + case NetMessageLibraryType.Ping: + if (NetUtility.BytesToHoldBits(payloadLengthBits) > 0) + HandleIncomingPing(m_owner.m_receiveBuffer[ptr]); + else + m_owner.LogWarning("Received malformed ping"); + break; + case NetMessageLibraryType.Pong: + if (payloadLengthBits == (9 * 8)) + { + byte pingNr = m_owner.m_receiveBuffer[ptr++]; + double remoteNetTime = BitConverter.ToDouble(m_owner.m_receiveBuffer, ptr); + HandleIncomingPong(now, pingNr, remoteNetTime); + } + else + { + m_owner.LogWarning("Received malformed pong"); + } + break; + case NetMessageLibraryType.Acknowledge: + HandleIncomingAcks(ptr, NetUtility.BytesToHoldBits(payloadLengthBits)); + break; + default: + throw new NotImplementedException("Unhandled library type: " + libType); + } + + return; + } + + public void SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method) + { + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + msg.m_type = (NetMessageType)method; + EnqueueOutgoingMessage(msg); + } + + public void SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + msg.m_type = (NetMessageType)((int)method + sequenceChannel); + EnqueueOutgoingMessage(msg); + } + + // called by user and network thread + internal void EnqueueOutgoingMessage(NetOutgoingMessage msg) + { + if (m_owner == null) + return; // we've been disposed + + int msgLen = msg.LengthBytes; + int mtu = m_owner.m_configuration.m_maximumTransmissionUnit; + + if (msgLen <= mtu) + { + Interlocked.Increment(ref msg.m_inQueueCount); + m_unsentMessages.Enqueue(msg); + return; + } + +#if DEBUG + if ((int)msg.m_type < (int)NetMessageType.UserReliableUnordered) + { + // unreliable + m_owner.LogWarning("Sending more than MTU (currently " + mtu + ") bytes unreliably is not recommended!"); + } +#endif + mtu -= NetConstants.FragmentHeaderSize; // size of fragmentation info + + // message must be fragmented + int fgi = Interlocked.Increment(ref m_nextFragmentGroupId); + + int numFragments = (msgLen + mtu - 1) / mtu; + + for(int i=0;i list = m_storedMessages[i]; + if (list != null) + { + try + { + foreach (NetOutgoingMessage om in list) + om.m_nextResendTime = (om.m_nextResendTime * 0.8) - 0.05; + } + catch (InvalidOperationException) + { + // ok, collection was modified, never mind then - it was worth a shot + } + } + } + + NetOutgoingMessage bye = m_owner.CreateLibraryMessage(NetMessageLibraryType.Disconnect, byeMessage); + EnqueueOutgoingMessage(bye); + } + + public void Approve() + { + if (!m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval)) + m_owner.LogError("Approve() called but ConnectionApproval is not enabled in NetPeerConfiguration!"); + + if (m_pendingStatus != PendingConnectionStatus.Pending) + { + m_owner.LogWarning("Approve() called on non-pending connection!"); + return; + } + m_pendingStatus = PendingConnectionStatus.Approved; + } + + public void Deny(string reason) + { + if (!m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval)) + m_owner.LogError("Deny() called but ConnectionApproval is not enabled in NetPeerConfiguration!"); + + if (m_pendingStatus != PendingConnectionStatus.Pending) + { + m_owner.LogWarning("Deny() called on non-pending connection!"); + return; + } + m_pendingStatus = PendingConnectionStatus.Denied; + m_pendingDenialReason = reason; + } + + internal void Dispose() + { + m_owner = null; + m_unsentMessages = null; + } + + public override string ToString() + { + return "[NetConnection to " + m_remoteEndpoint + ": " + m_status + "]"; + } + } +} diff --git a/Lidgren.Network/NetConnectionStatistics.cs b/Lidgren.Network/NetConnectionStatistics.cs new file mode 100644 index 0000000..092e62c --- /dev/null +++ b/Lidgren.Network/NetConnectionStatistics.cs @@ -0,0 +1,104 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + public sealed class NetConnectionStatistics + { + private NetConnection m_connection; + + internal int m_sentPackets; + internal int m_receivedPackets; + + internal int m_sentMessages; + internal int m_receivedMessages; + + internal int m_sentBytes; + internal int m_receivedBytes; + + internal NetConnectionStatistics(NetConnection conn) + { + m_connection = conn; + Reset(); + } + + internal void Reset() + { + m_sentPackets = 0; + m_receivedPackets = 0; + m_sentBytes = 0; + m_receivedBytes = 0; + } + + /// + /// Gets the number of sent packets for this connection + /// + public int SentPackets { get { return m_sentPackets; } } + + /// + /// Gets the number of received packets for this connection + /// + public int ReceivedPackets { get { return m_receivedPackets; } } + + /// + /// Gets the number of sent bytes for this connection + /// + public int SentBytes { get { return m_sentBytes; } } + + /// + /// Gets the number of received bytes for this connection + /// + public int ReceivedBytes { get { return m_receivedBytes; } } + + [Conditional("DEBUG")] + internal void PacketSent(int numBytes, int numMessages) + { + m_sentPackets++; + m_sentBytes += numBytes; + m_sentMessages += numMessages; + } + + [Conditional("DEBUG")] + internal void PacketReceived(int numBytes, int numMessages) + { + m_receivedPackets++; + m_receivedBytes += numBytes; + m_receivedMessages += numMessages; + } + + public override string ToString() + { + StringBuilder bdr = new StringBuilder(); + bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime)); + bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets"); + bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages in " + m_receivedPackets + " packets"); + int numUnsent = m_connection.m_unsentMessages.Count; + if (numUnsent > 0) + bdr.AppendLine("Unsent messages: " + numUnsent); + int numStored = m_connection.GetStoredMessagesCount(); + if (numStored > 0) + bdr.AppendLine("Stored messages: " + numStored); + return bdr.ToString(); + } + } +} diff --git a/Lidgren.Network/NetConnectionStatus.cs b/Lidgren.Network/NetConnectionStatus.cs new file mode 100644 index 0000000..7169538 --- /dev/null +++ b/Lidgren.Network/NetConnectionStatus.cs @@ -0,0 +1,34 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + /// + /// Status of a connection + /// + public enum NetConnectionStatus + { + None, + Connecting, + Connected, + Disconnecting, + Disconnected + } +} diff --git a/Lidgren.Network/NetConstants.cs b/Lidgren.Network/NetConstants.cs new file mode 100644 index 0000000..62d6650 --- /dev/null +++ b/Lidgren.Network/NetConstants.cs @@ -0,0 +1,44 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + internal static class NetConstants + { + internal const int NetChannelsPerDeliveryMethod = 32; + + internal const int NumSequenceNumbers = ushort.MaxValue + 1; // 0 is a valid sequence number + + /// + /// Number of channels which needs a sequence number to work + /// + internal const int NumSequencedChannels = ((int)NetMessageType.UserReliableOrdered + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced; + + /// + /// Number of reliable channels + /// + internal const int NumReliableChannels = ((int)NetMessageType.UserReliableOrdered + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserReliableUnordered; + + /// + /// Number of bytes added when message is really a fragment + /// + internal const int FragmentHeaderSize = 6; + } +} diff --git a/Lidgren.Network/NetEncryption.cs b/Lidgren.Network/NetEncryption.cs new file mode 100644 index 0000000..20d99ab --- /dev/null +++ b/Lidgren.Network/NetEncryption.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Lidgren.Network +{ + public sealed class NetXTEA + { + private const int m_blockSize = 8; + private const int m_keySize = 16; + private const int m_delta = unchecked((int)0x9E3779B9); + private const int m_dSum = unchecked((int)0xC6EF3720); // sum on decrypt + + private byte[] m_keyBytes; + private int[] m_key; + private int m_rounds; + + public byte[] Key { get { return m_keyBytes; } } + + /// + /// 16 byte key + /// + public NetXTEA(byte[] key, int rounds) + { + m_keyBytes = key; + m_key = new int[4]; + m_key[0] = BitConverter.ToInt32(key, 0); + m_key[1] = BitConverter.ToInt32(key, 4); + m_key[2] = BitConverter.ToInt32(key, 8); + m_key[3] = BitConverter.ToInt32(key, 12); + m_rounds = rounds; + } + + public void EncryptBlock( + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + // Pack bytes into integers + int v0 = BytesToInt(inBytes, inOff); + int v1 = BytesToInt(inBytes, inOff + 4); + + int sum = 0; + + for (int i = 0; i != m_rounds; i++) + { + v0 += ((v1 << 4 ^ (int)((uint)v1 >> 5)) + v1) ^ (sum + m_key[sum & 3]); + sum += m_delta; + v1 += ((v0 << 4 ^ (int)((uint)v0 >> 5)) + v0) ^ (sum + m_key[(int)((uint)sum >> 11) & 3]); + } + + UnpackInt(v0, outBytes, outOff); + UnpackInt(v1, outBytes, outOff + 4); + + return; + } + + public void DecryptBlock( + byte[] inBytes, + int inOff, + byte[] outBytes, + int outOff) + { + // Pack bytes into integers + int v0 = BytesToInt(inBytes, inOff); + int v1 = BytesToInt(inBytes, inOff + 4); + + int sum = m_dSum; + + for (int i = 0; i != m_rounds; i++) + { + v1 -= ((v0 << 4 ^ (int)((uint)v0 >> 5)) + v0) ^ (sum + m_key[(int)((uint)sum >> 11) & 3]); + sum -= m_delta; + v0 -= ((v1 << 4 ^ (int)((uint)v1 >> 5)) + v1) ^ (sum + m_key[sum & 3]); + } + + UnpackInt(v0, outBytes, outOff); + UnpackInt(v1, outBytes, outOff + 4); + + return; + } + + private static int BytesToInt(byte[] b, int inOff) + { + //return BitConverter.ToInt32(b, inOff); + return ((b[inOff++]) << 24) | + ((b[inOff++] & 255) << 16) | + ((b[inOff++] & 255) << 8) | + ((b[inOff] & 255)); + } + + private static void UnpackInt( + int v, + byte[] b, + int outOff) + { + uint uv = (uint)v; + b[outOff++] = (byte)(uv >> 24); + b[outOff++] = (byte)(uv >> 16); + b[outOff++] = (byte)(uv >> 8); + b[outOff] = (byte)uv; + } + } + + public static class NetSHA + { + // TODO: switch to SHA256 + private static SHA1 m_sha; + + public static byte[] Hash(byte[] data) + { + if (m_sha == null) + m_sha = SHA1Managed.Create(); + return m_sha.ComputeHash(data); + } + } + + public static class NetSRP + { + private static readonly BigInteger N = new BigInteger(NetUtility.ToByteArray("0115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3")); + private static readonly BigInteger g = new BigInteger((uint)2); + + /// + /// Creates a verifier that the server can use to authenticate users later on + /// + public static byte[] ComputePasswordVerifier(string username, string password, byte[] salt) + { + byte[] tmp = Encoding.ASCII.GetBytes(username + ":" + password); + byte[] innerHash = NetSHA.Hash(tmp); + + byte[] total = new byte[innerHash.Length + salt.Length]; + Buffer.BlockCopy(salt, 0, total, 0, salt.Length); + Buffer.BlockCopy(innerHash, 0, total, salt.Length, innerHash.Length); + + byte[] x = NetSHA.Hash(total); + + // Verifier (v) = g^x (mod N) + BigInteger xx = new BigInteger(x); + return g.ModPow(xx, N).GetBytes(); + } + } +} diff --git a/Lidgren.Network/NetException.cs b/Lidgren.Network/NetException.cs new file mode 100644 index 0000000..3cfc6ef --- /dev/null +++ b/Lidgren.Network/NetException.cs @@ -0,0 +1,69 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Runtime.Serialization; +using System.Diagnostics; + +namespace Lidgren.Network +{ + [Serializable] + public sealed class NetException : Exception + { + public NetException() + : base() + { + } + + public NetException(string message) + : base(message) + { + } + + public NetException(string message, Exception inner) + : base(message, inner) + { + } + + private NetException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Throws an exception, in DEBUG only, if first parameter is false + /// + [Conditional("DEBUG")] + public static void Assert(bool isOk, string message) + { + if (!isOk) + throw new NetException(message); + } + + /// + /// Throws an exception, in DEBUG only, if first parameter is false + /// + [Conditional("DEBUG")] + public static void Assert(bool isOk) + { + if (!isOk) + throw new NetException(); + } + } +} diff --git a/Lidgren.Network/NetFragmentationInfo.cs b/Lidgren.Network/NetFragmentationInfo.cs new file mode 100644 index 0000000..b073885 --- /dev/null +++ b/Lidgren.Network/NetFragmentationInfo.cs @@ -0,0 +1,12 @@ +using System; + +namespace Lidgren.Network +{ + public class NetFragmentationInfo + { + public int TotalFragmentCount; + public bool[] Received; + public int TotalReceived; + public int FragmentSize; + } +} diff --git a/Lidgren.Network/NetIncomingMessage.Peek.cs b/Lidgren.Network/NetIncomingMessage.Peek.cs new file mode 100644 index 0000000..66e18bd --- /dev/null +++ b/Lidgren.Network/NetIncomingMessage.Peek.cs @@ -0,0 +1,262 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics; +using System.Net; + +namespace Lidgren.Network +{ + public partial class NetIncomingMessage + { + // + // 1 bit + // + public bool PeekBoolean() + { + NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition); + return (retval > 0 ? true : false); + } + + // + // 8 bit + // + public byte PeekByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + return retval; + } + + [CLSCompliant(false)] + public sbyte PeekSByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + return (sbyte)retval; + } + + public byte PeekByte(int numberOfBits) + { + byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition); + return retval; + } + + public byte[] PeekBytes(int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); + + byte[] retval = new byte[numberOfBytes]; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0); + return retval; + } + + public void PeekBytes(byte[] into, int offset, int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); + NetException.Assert(offset + numberOfBytes <= into.Length); + + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset); + return; + } + + // + // 16 bit + // + public Int16 PeekInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 16, m_readPosition); + return (short)retval; + } + + [CLSCompliant(false)] + public UInt16 PeekUInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 16, m_readPosition); + return (ushort)retval; + } + + // + // 32 bit + // + public Int32 PeekInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + return (Int32)retval; + } + + public Int32 PeekInt32(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadInt() can only read between 1 and 32 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + + if (numberOfBits == 32) + return (int)retval; + + int signBit = 1 << (numberOfBits - 1); + if ((retval & signBit) == 0) + return (int)retval; // positive + + // negative + unchecked + { + uint mask = ((uint)-1) >> (33 - numberOfBits); + uint tmp = (retval & mask) + 1; + return -((int)tmp); + } + } + + [CLSCompliant(false)] + public UInt32 PeekUInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + return retval; + } + + [CLSCompliant(false)] + public UInt32 PeekUInt32(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadUInt() can only read between 1 and 32 bits"); + //NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size"); + + UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + return retval; + } + + // + // 64 bit + // + [CLSCompliant(false)] + public UInt64 PeekUInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition + 32); + + ulong retval = low + (high << 32); + + return retval; + } + + public Int64 PeekInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + unchecked + { + ulong retval = PeekUInt64(); + long longRetval = (long)retval; + return longRetval; + } + } + + [CLSCompliant(false)] + public UInt64 PeekUInt64(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 64), "ReadUInt() can only read between 1 and 64 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + ulong retval; + if (numberOfBits <= 32) + { + retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + } + else + { + retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + retval |= NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition) << 32; + } + return retval; + } + + public Int64 PeekInt64(int numberOfBits) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits < 65)), "ReadInt64(bits) can only read between 1 and 64 bits"); + return (long)PeekUInt64(numberOfBits); + } + + // + // Floating point + // + public float PeekFloat() + { + return PeekSingle(); + } + + public float PeekSingle() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + // endianness is handled inside BitConverter.ToSingle + float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3); + return retval; + } + + byte[] bytes = PeekBytes(4); + return BitConverter.ToSingle(bytes, 0); // endianness is handled inside BitConverter.ToSingle + } + + public double PeekDouble() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + // read directly + double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3); + return retval; + } + + byte[] bytes = PeekBytes(8); + return BitConverter.ToDouble(bytes, 0); // endianness is handled inside BitConverter.ToSingle + } + + /// + /// Reads a string + /// + public string PeekString() + { + int byteLen = (int)ReadVariableUInt32(); + + if (byteLen == 0) + return String.Empty; + + NetException.Assert(m_bitLength - m_readPosition >= (byteLen * 8), c_readOverflowError); + + if ((m_readPosition & 7) == 0) + { + // read directly + string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen); + return retval; + } + + byte[] bytes = PeekBytes(byteLen); + return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + } +} diff --git a/Lidgren.Network/NetIncomingMessage.Read.cs b/Lidgren.Network/NetIncomingMessage.Read.cs new file mode 100644 index 0000000..d58dcab --- /dev/null +++ b/Lidgren.Network/NetIncomingMessage.Read.cs @@ -0,0 +1,548 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics; +using System.Net; +using System.Reflection; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + public partial class NetIncomingMessage + { + private const string c_readOverflowError = "Trying to read past the buffer size - likely caused by mismatching Write/Reads, different size or order."; + + private static Dictionary s_readMethods; + + private int m_readPosition; + + /// + /// Gets or sets the read position in the buffer, in bits (not bytes) + /// + public int Position + { + get { return m_readPosition; } + set { m_readPosition = value; } + } + + static NetIncomingMessage() + { + Type[] integralTypes = typeof(Byte).Assembly.GetTypes(); + + s_readMethods = new Dictionary(); + MethodInfo[] methods = typeof(NetIncomingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public); + foreach (MethodInfo mi in methods) + { + if (mi.GetParameters().Length == 0 && mi.Name.StartsWith("Read", StringComparison.InvariantCulture)) + { + string n = mi.Name.Substring(4); + foreach (Type it in integralTypes) + { + if (it.Name == n) + s_readMethods[it] = mi; + } + } + } + } + + // + // 1 bit + // + public bool ReadBoolean() + { + NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition); + m_readPosition += 1; + return (retval > 0 ? true : false); + } + + // + // 8 bit + // + public byte ReadByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + m_readPosition += 8; + return retval; + } + + [CLSCompliant(false)] + public sbyte ReadSByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + m_readPosition += 8; + return (sbyte)retval; + } + + public byte ReadByte(int numberOfBits) + { + byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + return retval; + } + + public byte[] ReadBytes(int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); + + byte[] retval = new byte[numberOfBytes]; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0); + m_readPosition += (8 * numberOfBytes); + return retval; + } + + public void ReadBytes(byte[] into, int offset, int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); + NetException.Assert(offset + numberOfBytes <= into.Length); + + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset); + m_readPosition += (8 * numberOfBytes); + return; + } + + public void ReadBits(byte[] into, int offset, int numberOfBits) + { + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + NetException.Assert(offset + NetUtility.BytesToHoldBits(numberOfBits) <= into.Length); + + int numberOfWholeBytes = numberOfBits / 8; + int extraBits = numberOfBits - (numberOfWholeBytes * 8); + + NetBitWriter.ReadBytes(m_data, numberOfWholeBytes, m_readPosition, into, offset); + m_readPosition += (8 * numberOfWholeBytes); + + if (extraBits > 0) + into[offset + numberOfWholeBytes] = ReadByte(extraBits); + + return; + } + + // + // 16 bit + // + public Int16 ReadInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 16, m_readPosition); + m_readPosition += 16; + return (short)retval; + } + + [CLSCompliant(false)] + public UInt16 ReadUInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 16, m_readPosition); + m_readPosition += 16; + return (ushort)retval; + } + + // + // 32 bit + // + public Int32 ReadInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return (Int32)retval; + } + + public Int32 ReadInt32(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadInt() can only read between 1 and 32 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + + if (numberOfBits == 32) + return (int)retval; + + int signBit = 1 << (numberOfBits - 1); + if ((retval & signBit) == 0) + return (int)retval; // positive + + // negative + unchecked + { + uint mask = ((uint)-1) >> (33 - numberOfBits); + uint tmp = (retval & mask) + 1; + return -((int)tmp); + } + } + + [CLSCompliant(false)] + public UInt32 ReadUInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return retval; + } + + [CLSCompliant(false)] + public UInt32 ReadUInt32(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadUInt() can only read between 1 and 32 bits"); + //NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size"); + + UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + return retval; + } + + // + // 64 bit + // + [CLSCompliant(false)] + public UInt64 ReadUInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + + ulong retval = low + (high << 32); + + m_readPosition += 32; + return retval; + } + + public Int64 ReadInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + unchecked + { + ulong retval = ReadUInt64(); + long longRetval = (long)retval; + return longRetval; + } + } + + [CLSCompliant(false)] + public UInt64 ReadUInt64(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 64), "ReadUInt() can only read between 1 and 64 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + ulong retval; + if (numberOfBits <= 32) + { + retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + } + else + { + retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + retval |= NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition) << 32; + } + m_readPosition += numberOfBits; + return retval; + } + + public Int64 ReadInt64(int numberOfBits) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits < 65)), "ReadInt64(bits) can only read between 1 and 64 bits"); + return (long)ReadUInt64(numberOfBits); + } + + // + // Floating point + // + public float ReadFloat() + { + return ReadSingle(); + } + + public float ReadSingle() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + // endianness is handled inside BitConverter.ToSingle + float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3); + m_readPosition += 32; + return retval; + } + + byte[] bytes = ReadBytes(4); + return BitConverter.ToSingle(bytes, 0); // endianness is handled inside BitConverter.ToSingle + } + + public double ReadDouble() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + // read directly + double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3); + m_readPosition += 64; + return retval; + } + + byte[] bytes = ReadBytes(8); + return BitConverter.ToDouble(bytes, 0); // endianness is handled inside BitConverter.ToSingle + } + + // + // Variable bit count + // + + /// + /// Reads a UInt32 written using WriteVariableUInt32() + /// + [CLSCompliant(false)] + public uint ReadVariableUInt32() + { + int num1 = 0; + int num2 = 0; + while (true) + { + if (num2 == 0x23) + throw new FormatException("Bad 7-bit encoded integer"); + + byte num3 = this.ReadByte(); + num1 |= (num3 & 0x7f) << num2; + num2 += 7; + if ((num3 & 0x80) == 0) + return (uint)num1; + } + } + + /// + /// Reads a Int32 written using WriteVariableInt32() + /// + public int ReadVariableInt32() + { + int num1 = 0; + int num2 = 0; + while (true) + { + if (num2 == 0x23) + throw new FormatException("Bad 7-bit encoded integer"); + + byte num3 = this.ReadByte(); + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + int sign = (num1 << 31) >> 31; + return sign ^ (num1 >> 1); + } + } + } + + /// + /// Reads a UInt32 written using WriteVariableInt64() + /// + [CLSCompliant(false)] + public UInt64 ReadVariableUInt64() + { + UInt64 num1 = 0; + int num2 = 0; + while (true) + { + if (num2 == 0x23) + throw new FormatException("Bad 7-bit encoded integer"); + + byte num3 = this.ReadByte(); + num1 |= ((UInt64)num3 & 0x7f) << num2; + num2 += 7; + if ((num3 & 0x80) == 0) + return num1; + } + } + + /// + /// Reads a float written using WriteSignedSingle() + /// + public float ReadSignedSingle(int numberOfBits) + { + uint encodedVal = ReadUInt32(numberOfBits); + int maxVal = (1 << numberOfBits) - 1; + return ((float)(encodedVal + 1) / (float)(maxVal + 1) - 0.5f) * 2.0f; + } + + /// + /// Reads a float written using WriteUnitSingle() + /// + public float ReadUnitSingle(int numberOfBits) + { + uint encodedVal = ReadUInt32(numberOfBits); + int maxVal = (1 << numberOfBits) - 1; + return (float)(encodedVal + 1) / (float)(maxVal + 1); + } + + /// + /// Reads a float written using WriteRangedSingle() using the same MIN and MAX values + /// + public float ReadRangedSingle(float min, float max, int numberOfBits) + { + float range = max - min; + int maxVal = (1 << numberOfBits) - 1; + float encodedVal = (float)ReadUInt32(numberOfBits); + float unit = encodedVal / (float)maxVal; + return min + (unit * range); + } + + /// + /// Reads an integer written using WriteRangedInteger() using the same min/max values + /// + public int ReadRangedInteger(int min, int max) + { + uint range = (uint)(max - min); + int numBits = NetUtility.BitsToHoldUInt(range); + + uint rvalue = ReadUInt32(numBits); + return (int)(min + rvalue); + } + + /// + /// Reads a string + /// + public string ReadString() + { + int byteLen = (int)ReadVariableUInt32(); + + if (byteLen == 0) + return String.Empty; + + NetException.Assert(m_bitLength - m_readPosition >= (byteLen * 8), c_readOverflowError); + + if ((m_readPosition & 7) == 0) + { + // read directly + string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen); + m_readPosition += (8 * byteLen); + return retval; + } + + byte[] bytes = ReadBytes(byteLen); + return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + /// + /// Reads a stored IPv4 endpoint description + /// + public IPEndPoint ReadIPEndpoint() + { + byte len = ReadByte(); + byte[] addressBytes = ReadBytes(len); + int port = (int)ReadUInt16(); + + IPAddress address = new IPAddress(addressBytes); + return new IPEndPoint(address, port); + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + public void SkipPadBits() + { + m_readPosition = ((m_readPosition + 7) >> 3) * 8; + } + + /// + /// Pads data with the specified number of bits. + /// + public void SkipPadBits(int numberOfBits) + { + m_readPosition += numberOfBits; + } + + /// + /// Reads all public and private declared instance fields of the object in declaration order using reflection + /// + public void ReadAllFields(object target) + { + ReadAllFields(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Reads all fields with the specified binding of the object in declaration order using reflection + /// + public void ReadAllFields(object target, BindingFlags flags) + { + if (target == null) + return; + Type tp = target.GetType(); + + FieldInfo[] fields = tp.GetFields(flags); + foreach (FieldInfo fi in fields) + { + object value; + + // find read method + MethodInfo readMethod; + if (s_readMethods.TryGetValue(fi.FieldType, out readMethod)) + { + // read value + value = readMethod.Invoke(this, null); + + // set the value + fi.SetValue(target, value); + } + } + } + + /// + /// Reads all public and private declared instance fields of the object in declaration order using reflection + /// + public void ReadAllProperties(object target) + { + ReadAllProperties(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Reads all fields with the specified binding of the object in declaration order using reflection + /// + public void ReadAllProperties(object target, BindingFlags flags) + { + if (target == null) + return; + Type tp = target.GetType(); + + PropertyInfo[] fields = tp.GetProperties(flags); + foreach (PropertyInfo fi in fields) + { + object value; + + // find read method + MethodInfo readMethod; + if (s_readMethods.TryGetValue(fi.PropertyType, out readMethod)) + { + // read value + value = readMethod.Invoke(this, null); + + // set the value + MethodInfo setMethod = fi.GetSetMethod((flags & BindingFlags.NonPublic) == BindingFlags.NonPublic); + setMethod.Invoke(target, new object[] { value }); + } + } + } + } +} + diff --git a/Lidgren.Network/NetIncomingMessage.Write.cs b/Lidgren.Network/NetIncomingMessage.Write.cs new file mode 100644 index 0000000..dfae042 --- /dev/null +++ b/Lidgren.Network/NetIncomingMessage.Write.cs @@ -0,0 +1,476 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Net; +using System.Diagnostics; +using System.Text; + +namespace Lidgren.Network +{ + public partial class NetIncomingMessage + { + /// + /// Ensures the buffer can hold this number of bits + /// + private void InternalEnsureBufferSize(int numberOfBits) + { + int byteLen = ((numberOfBits + 7) >> 3); + if (m_data == null) + { + m_data = new byte[byteLen]; + return; + } + if (m_data.Length < byteLen) + Array.Resize(ref m_data, byteLen); + return; + } + + // + // 1 bit + // + internal void Write(bool value) + { + InternalEnsureBufferSize(m_bitLength + 1); + NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength); + m_bitLength += 1; + } + + // + // 8 bit + // + internal void Write(byte source) + { + InternalEnsureBufferSize(m_bitLength + 8); + NetBitWriter.WriteByte(source, 8, m_data, m_bitLength); + m_bitLength += 8; + } + + internal void Write(sbyte source) + { + InternalEnsureBufferSize(m_bitLength + 8); + NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength); + m_bitLength += 8; + } + + internal void Write(byte source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits"); + InternalEnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + internal void Write(byte[] source) + { + if (source == null) + throw new ArgumentNullException("source"); + int bits = source.Length * 8; + InternalEnsureBufferSize(m_bitLength + bits); + NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength); + m_bitLength += bits; + } + + internal void Write(byte[] source, int offsetInBytes, int numberOfBytes) + { + if (source == null) + throw new ArgumentNullException("source"); + int bits = numberOfBytes * 8; + InternalEnsureBufferSize(m_bitLength + bits); + NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength); + m_bitLength += bits; + } + + // + // 16 bit + // + internal void Write(UInt16 source) + { + InternalEnsureBufferSize(m_bitLength + 16); + NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength); + m_bitLength += 16; + } + + internal void Write(UInt16 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits"); + InternalEnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + internal void Write(Int16 source) + { + InternalEnsureBufferSize(m_bitLength + 16); + NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength); + m_bitLength += 16; + } + + // + // 32 bit + // +#if UNSAFE + internal unsafe void Write(Int32 source) + { + EnsureBufferSize(m_bitLength + 32); + + // can write fast? + if (m_bitLength % 8 == 0) + { + fixed (byte* numRef = &Data[m_bitLength / 8]) + { + *((int*)numRef) = source; + } + } + else + { + NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength); + } + m_bitLength += 32; + } +#else + internal void Write(Int32 source) + { + InternalEnsureBufferSize(m_bitLength + 32); + NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength); + m_bitLength += 32; + } +#endif + +#if UNSAFE + internal unsafe void Write(UInt32 source) + { + EnsureBufferSize(m_bitLength + 32); + + // can write fast? + if (m_bitLength % 8 == 0) + { + fixed (byte* numRef = &Data[m_bitLength / 8]) + { + *((uint*)numRef) = source; + } + } + else + { + NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength); + } + + m_bitLength += 32; + } +#else + internal void Write(UInt32 source) + { + InternalEnsureBufferSize(m_bitLength + 32); + NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength); + m_bitLength += 32; + } +#endif + + internal void Write(UInt32 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits"); + InternalEnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + internal void Write(Int32 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits"); + InternalEnsureBufferSize(m_bitLength + numberOfBits); + + if (numberOfBits != 32) + { + // make first bit sign + int signBit = 1 << (numberOfBits - 1); + if (source < 0) + source = (-source - 1) | signBit; + else + source &= (~signBit); + } + + NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength); + + m_bitLength += numberOfBits; + } + + // + // 64 bit + // + internal void Write(UInt64 source) + { + InternalEnsureBufferSize(m_bitLength + 64); + NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength); + m_bitLength += 64; + } + + internal void Write(UInt64 source, int numberOfBits) + { + InternalEnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + internal void Write(Int64 source) + { + InternalEnsureBufferSize(m_bitLength + 64); + ulong usource = (ulong)source; + NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength); + m_bitLength += 64; + } + + internal void Write(Int64 source, int numberOfBits) + { + InternalEnsureBufferSize(m_bitLength + numberOfBits); + ulong usource = (ulong)source; + NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + // + // Floating point + // +#if UNSAFE + internal unsafe void Write(float source) + { + uint val = *((uint*)&source); +#if BIGENDIAN + val = NetUtility.SwapByteOrder(val); +#endif + Write(val); + } +#else + internal void Write(float source) + { + byte[] val = BitConverter.GetBytes(source); +#if BIGENDIAN + // swap byte order + byte tmp = val[3]; + val[3] = val[0]; + val[0] = tmp; + tmp = val[2]; + val[2] = val[1]; + val[1] = tmp; +#endif + Write(val); + } +#endif + +#if UNSAFE + internal unsafe void Write(double source) + { + ulong val = *((ulong*)&source); +#if BIGENDIAN + val = NetUtility.SwapByteOrder(val); +#endif + Write(val); + } +#else + internal void Write(double source) + { + byte[] val = BitConverter.GetBytes(source); +#if BIGENDIAN + // 0 1 2 3 4 5 6 7 + + // swap byte order + byte tmp = val[7]; + val[7] = val[0]; + val[0] = tmp; + + tmp = val[6]; + val[6] = val[1]; + val[1] = tmp; + + tmp = val[5]; + val[5] = val[2]; + val[2] = tmp; + + tmp = val[4]; + val[4] = val[3]; + val[3] = tmp; +#endif + Write(val); + } +#endif + + // + // Variable bits + // + + /// + /// Write Base128 encoded variable sized unsigned integer + /// + /// number of bytes written + internal int WriteVariableUInt32(uint value) + { + int retval = 1; + uint num1 = (uint)value; + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Write Base128 encoded variable sized signed integer + /// + /// number of bytes written + internal int WriteVariableInt32(int value) + { + int retval = 1; + uint num1 = (uint)((value << 1) ^ (value >> 31)); + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Write Base128 encoded variable sized unsigned integer + /// + /// number of bytes written + internal int WriteVariableUInt64(UInt64 value) + { + int retval = 1; + UInt64 num1 = (UInt64)value; + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Compress (lossy) a float in the range -1..1 using numberOfBits bits + /// + internal void WriteSignedSingle(float value, int numberOfBits) + { + NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value); + + float unit = (value + 1.0f) * 0.5f; + int maxVal = (1 << numberOfBits) - 1; + uint writeVal = (uint)(unit * (float)maxVal); + + Write(writeVal, numberOfBits); + } + + /// + /// Compress (lossy) a float in the range 0..1 using numberOfBits bits + /// + internal void WriteUnitSingle(float value, int numberOfBits) + { + NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value); + + int maxValue = (1 << numberOfBits) - 1; + uint writeVal = (uint)(value * (float)maxValue); + + Write(writeVal, numberOfBits); + } + + /// + /// Compress a float within a specified range using a certain number of bits + /// + internal void WriteRangedSingle(float value, float min, float max, int numberOfBits) + { + NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value); + + float range = max - min; + float unit = ((value - min) / range); + int maxVal = (1 << numberOfBits) - 1; + Write((UInt32)((float)maxVal * unit), numberOfBits); + } + + /// + /// Writes an integer with the least amount of bits need for the specified range + /// Returns number of bits written + /// + internal int WriteRangedInteger(int min, int max, int value) + { + NetException.Assert(value >= min && value <= max, "Value not within min/max range!"); + + uint range = (uint)(max - min); + int numBits = NetUtility.BitsToHoldUInt(range); + + uint rvalue = (uint)(value - min); + Write(rvalue, numBits); + + return numBits; + } + + /// + /// Write a string + /// + internal void Write(string source) + { + if (string.IsNullOrEmpty(source)) + { + InternalEnsureBufferSize(m_bitLength + 8); + WriteVariableUInt32(0); + return; + } + + byte[] bytes = Encoding.UTF8.GetBytes(source); + InternalEnsureBufferSize(m_bitLength + (bytes.Length > 127 ? 2 * 8 : 1 * 8) + (bytes.Length * 8)); + WriteVariableUInt32((uint)bytes.Length); + Write(bytes); + } + + /// + /// Writes an endpoint description + /// + /// + internal void Write(IPEndPoint endPoint) + { + byte[] bytes = endPoint.Address.GetAddressBytes(); + Write((byte)bytes.Length); + Write(bytes); + Write((ushort)endPoint.Port); + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + internal void WritePadBits() + { + m_bitLength = ((m_bitLength + 7) / 8) * 8; + InternalEnsureBufferSize(m_bitLength); + } + + /// + /// Pads data with the specified number of bits. + /// + internal void WritePadBits(int numberOfBits) + { + m_bitLength += numberOfBits; + InternalEnsureBufferSize(m_bitLength); + } + } +} diff --git a/Lidgren.Network/NetIncomingMessage.cs b/Lidgren.Network/NetIncomingMessage.cs new file mode 100644 index 0000000..1611d13 --- /dev/null +++ b/Lidgren.Network/NetIncomingMessage.cs @@ -0,0 +1,123 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Net; +using System.Diagnostics; + +namespace Lidgren.Network +{ + internal enum NetIncomingMessageReleaseStatus + { + NotReleased = 0, + ReleasedToApplication, + RecycledByApplication + } + + [DebuggerDisplay("{m_readPosition} of {m_bitLength} bits ({LengthBytes} bytes) read")] + public partial class NetIncomingMessage + { + internal byte[] m_data; + internal int m_bitLength; + internal NetMessageType m_messageType; // NetDeliveryMethod and sequence channel can be derived from this + internal ushort m_sequenceNumber; + internal NetIncomingMessageReleaseStatus m_status; + + internal NetIncomingMessageType m_incomingType; + internal IPEndPoint m_senderEndpoint; + internal NetConnection m_senderConnection; + + internal NetFragmentationInfo m_fragmentationInfo; + + /// + /// Gets the length of the data in number of bytes + /// + public int LengthBytes + { + get { return ((m_bitLength + 7) >> 3); } + } + + /// + /// Gets the length of the data in number of bits + /// + public int LengthBits + { + get { return m_bitLength; } + } + + /// + /// Gets the NetDeliveryMethod used by this message + /// + public NetDeliveryMethod DeliveryMethod + { + get { return NetPeer.GetDeliveryMethod(m_messageType); } + } + + /// + /// Gets which sequence channel this message was sent in + /// + public int SequenceChannel + { + get { return (int)m_messageType - (int)NetPeer.GetDeliveryMethod(m_messageType); } + } + + /// + /// Type of data contained in this message + /// + public NetIncomingMessageType MessageType { get { return m_incomingType; } } + + /// + /// IPEndPoint of sender, if any + /// + public IPEndPoint SenderEndpoint { get { return m_senderEndpoint; } } + + /// + /// NetConnection of sender, if any + /// + public NetConnection SenderConnection { get { return m_senderConnection; } } + + internal NetIncomingMessage() + { + } + + internal NetIncomingMessage(byte[] data, int dataLength) + { + m_data = data; + m_bitLength = dataLength * 8; + } + + internal void Reset() + { + m_bitLength = 0; + m_readPosition = 0; + m_fragmentationInfo = null; + } + + public override string ToString() + { + return String.Format("[NetIncomingMessage {0}, {1}|{2}, {3} bits]", + m_incomingType, + m_messageType, + m_sequenceNumber, + m_bitLength + ); + } + } +} diff --git a/Lidgren.Network/NetIncomingMessageType.cs b/Lidgren.Network/NetIncomingMessageType.cs new file mode 100644 index 0000000..c4b3414 --- /dev/null +++ b/Lidgren.Network/NetIncomingMessageType.cs @@ -0,0 +1,45 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Lidgren.Network +{ + /// + /// Type of incoming message + /// + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] + public enum NetIncomingMessageType + { + // library note: values are power-of-two, but they are not flags - it's a convenience for NetPeerConfiguration.DisabledMessageTypes + Error = 0, + StatusChanged = 1 << 0, // Data (string) + UnconnectedData = 1 << 1, // Data Based on data received + ConnectionApproval = 1 << 2, // Data + Data = 1 << 3, // Data Based on data received + Receipt = 1 << 4, // Data + DiscoveryRequest = 1 << 5, // (no data) + DiscoveryResponse = 1 << 6, // Data + VerboseDebugMessage = 1 << 7, // Data (string) + DebugMessage = 1 << 8, // Data (string) + WarningMessage = 1 << 9, // Data (string) + ErrorMessage = 1 << 10, // Data (string) + } +} diff --git a/Lidgren.Network/NetMessageType.cs b/Lidgren.Network/NetMessageType.cs new file mode 100644 index 0000000..7a726af --- /dev/null +++ b/Lidgren.Network/NetMessageType.cs @@ -0,0 +1,166 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + // publicly visible subset of NetMessageType + + /// + /// How the library deals with dropped and delayed messages + /// + public enum NetDeliveryMethod : byte + { + Unknown = 0, + Unreliable = 2, + UnreliableSequenced = 3, + ReliableUnordered = 35, + ReliableSequenced = 36, + ReliableOrdered = 68, + } + + internal enum NetMessageLibraryType : byte + { + Error = 0, + KeepAlive = 1, // used for piggybacking acks + Ping = 2, // used for RTT calculation + Pong = 3, // used for RTT calculation + Connect = 4, + ConnectResponse = 5, + ConnectionEstablished = 6, + Acknowledge = 7, + Disconnect = 8, + Discovery = 9, + DiscoveryResponse = 10, + NatIntroduction = 11, + } + + internal enum NetMessageType : byte + { + Error = 0, + + Library = 1, // NetMessageLibraryType byte follows + + UserUnreliable = 2, + + // 3 to 34 = UserSequenced 0 to 31 + UserSequenced = 3, + UserSequenced1 = 4, + UserSequenced2 = 5, + UserSequenced3 = 6, + UserSequenced4 = 7, + UserSequenced5 = 8, + UserSequenced6 = 9, + UserSequenced7 = 10, + UserSequenced8 = 11, + UserSequenced9 = 12, + UserSequenced10 = 13, + UserSequenced11 = 14, + UserSequenced12 = 15, + UserSequenced13 = 16, + UserSequenced14 = 17, + UserSequenced15 = 18, + UserSequenced16 = 19, + UserSequenced17 = 20, + UserSequenced18 = 21, + UserSequenced19 = 22, + UserSequenced20 = 23, + UserSequenced21 = 24, + UserSequenced22 = 25, + UserSequenced23 = 26, + UserSequenced24 = 27, + UserSequenced25 = 28, + UserSequenced26 = 29, + UserSequenced27 = 30, + UserSequenced28 = 31, + UserSequenced29 = 32, + UserSequenced30 = 33, + UserSequenced31 = 34, + + UserReliableUnordered = 35, + + // 36 to 67 = UserReliableSequenced 0 to 31 + UserReliableSequenced = 36, + UserReliableSequenced1 = 37, + UserReliableSequenced2 = 38, + UserReliableSequenced3 = 39, + UserReliableSequenced4 = 40, + UserReliableSequenced5 = 41, + UserReliableSequenced6 = 42, + UserReliableSequenced7 = 43, + UserReliableSequenced8 = 44, + UserReliableSequenced9 = 45, + UserReliableSequenced10 = 46, + UserReliableSequenced11 = 47, + UserReliableSequenced12 = 48, + UserReliableSequenced13 = 49, + UserReliableSequenced14 = 50, + UserReliableSequenced15 = 51, + UserReliableSequenced16 = 52, + UserReliableSequenced17 = 53, + UserReliableSequenced18 = 54, + UserReliableSequenced19 = 55, + UserReliableSequenced20 = 56, + UserReliableSequenced21 = 57, + UserReliableSequenced22 = 58, + UserReliableSequenced23 = 59, + UserReliableSequenced24 = 60, + UserReliableSequenced25 = 61, + UserReliableSequenced26 = 62, + UserReliableSequenced27 = 63, + UserReliableSequenced28 = 64, + UserReliableSequenced29 = 65, + UserReliableSequenced30 = 66, + UserReliableSequenced31 = 67, + + // 68 to 99 = UserReliableOrdered 0 to 31 + UserReliableOrdered = 68, + UserReliableOrdered1 = 69, + UserReliableOrdered2 = 70, + UserReliableOrdered3 = 71, + UserReliableOrdered4 = 72, + UserReliableOrdered5 = 73, + UserReliableOrdered6 = 74, + UserReliableOrdered7 = 75, + UserReliableOrdered8 = 76, + UserReliableOrdered9 = 77, + UserReliableOrdered10 = 78, + UserReliableOrdered11 = 79, + UserReliableOrdered12 = 80, + UserReliableOrdered13 = 81, + UserReliableOrdered14 = 82, + UserReliableOrdered15 = 83, + UserReliableOrdered16 = 84, + UserReliableOrdered17 = 85, + UserReliableOrdered18 = 86, + UserReliableOrdered19 = 87, + UserReliableOrdered20 = 88, + UserReliableOrdered21 = 89, + UserReliableOrdered22 = 90, + UserReliableOrdered23 = 91, + UserReliableOrdered24 = 92, + UserReliableOrdered25 = 93, + UserReliableOrdered26 = 94, + UserReliableOrdered27 = 95, + UserReliableOrdered28 = 96, + UserReliableOrdered29 = 97, + UserReliableOrdered30 = 98, + UserReliableOrdered31 = 99, + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetOutgoingMessage.Write.cs b/Lidgren.Network/NetOutgoingMessage.Write.cs new file mode 100644 index 0000000..b084937 --- /dev/null +++ b/Lidgren.Network/NetOutgoingMessage.Write.cs @@ -0,0 +1,618 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Diagnostics; +using System.Text; +using System.Reflection; + +namespace Lidgren.Network +{ + public sealed partial class NetOutgoingMessage + { + private const int c_overAllocateAmount = 4; + + private static Dictionary s_writeMethods; + + internal byte[] m_data; + internal int m_bitLength; + + static NetOutgoingMessage() + { + s_writeMethods = new Dictionary(); + MethodInfo[] methods = typeof(NetOutgoingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public); + foreach (MethodInfo mi in methods) + { + if (mi.Name == "Write") + { + ParameterInfo[] pis = mi.GetParameters(); + if (pis.Length == 1) + s_writeMethods[pis[0].ParameterType] = mi; + } + } + } + + /// + /// Returns the internal data buffer, don't modify + /// + public byte[] PeekDataBuffer() + { + return m_data; + } + + /// + /// Gets or sets the length of the buffer in bytes + /// + public int LengthBytes + { + get { return ((m_bitLength + 7) >> 3); } + set + { + m_bitLength = value * 8; + InternalEnsureBufferSize(m_bitLength); + } + } + + /// + /// Gets or sets the length of the buffer in bits + /// + public int LengthBits + { + get { return m_bitLength; } + set + { + m_bitLength = value; + InternalEnsureBufferSize(m_bitLength); + } + } + + /// + /// Ensures the buffer can hold this number of bits + /// + public void EnsureBufferSize(int numberOfBits) + { + int byteLen = ((numberOfBits + 7) >> 3); + if (m_data == null) + { + m_data = new byte[byteLen + c_overAllocateAmount]; + return; + } + if (m_data.Length < byteLen) + Array.Resize(ref m_data, byteLen + c_overAllocateAmount); + return; + } + + /// + /// Ensures the buffer can hold this number of bits + /// + public void InternalEnsureBufferSize(int numberOfBits) + { + int byteLen = ((numberOfBits + 7) >> 3); + if (m_data == null) + { + m_data = new byte[byteLen]; + return; + } + if (m_data.Length < byteLen) + Array.Resize(ref m_data, byteLen); + return; + } + + // + // 1 bit + // + public void Write(bool value) + { + EnsureBufferSize(m_bitLength + 1); + NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength); + m_bitLength += 1; + } + + // + // 8 bit + // + public void Write(byte source) + { + EnsureBufferSize(m_bitLength + 8); + NetBitWriter.WriteByte(source, 8, m_data, m_bitLength); + m_bitLength += 8; + } + + [CLSCompliant(false)] + public void Write(sbyte source) + { + EnsureBufferSize(m_bitLength + 8); + NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength); + m_bitLength += 8; + } + + public void Write(byte source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + public void Write(byte[] source) + { + if (source == null) + throw new ArgumentNullException("source"); + int bits = source.Length * 8; + EnsureBufferSize(m_bitLength + bits); + NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength); + m_bitLength += bits; + } + + public void Write(byte[] source, int offsetInBytes, int numberOfBytes) + { + if (source == null) + throw new ArgumentNullException("source"); + int bits = numberOfBytes * 8; + EnsureBufferSize(m_bitLength + bits); + NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength); + m_bitLength += bits; + } + + // + // 16 bit + // + [CLSCompliant(false)] + public void Write(UInt16 source) + { + EnsureBufferSize(m_bitLength + 16); + NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength); + m_bitLength += 16; + } + + [CLSCompliant(false)] + public void Write(UInt16 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + public void Write(Int16 source) + { + EnsureBufferSize(m_bitLength + 16); + NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength); + m_bitLength += 16; + } + + // + // 32 bit + // +#if UNSAFE + public unsafe void Write(Int32 source) + { + EnsureBufferSize(m_bitLength + 32); + + // can write fast? + if (m_bitLength % 8 == 0) + { + fixed (byte* numRef = &Data[m_bitLength / 8]) + { + *((int*)numRef) = source; + } + } + else + { + NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength); + } + m_bitLength += 32; + } +#else + public void Write(Int32 source) + { + EnsureBufferSize(m_bitLength + 32); + NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength); + m_bitLength += 32; + } +#endif + +#if UNSAFE + public unsafe void Write(UInt32 source) + { + EnsureBufferSize(m_bitLength + 32); + + // can write fast? + if (m_bitLength % 8 == 0) + { + fixed (byte* numRef = &Data[m_bitLength / 8]) + { + *((uint*)numRef) = source; + } + } + else + { + NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength); + } + + m_bitLength += 32; + } +#else + [CLSCompliant(false)] + public void Write(UInt32 source) + { + EnsureBufferSize(m_bitLength + 32); + NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength); + m_bitLength += 32; + } +#endif + + [CLSCompliant(false)] + public void Write(UInt32 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + public void Write(Int32 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + + if (numberOfBits != 32) + { + // make first bit sign + int signBit = 1 << (numberOfBits - 1); + if (source < 0) + source = (-source - 1) | signBit; + else + source &= (~signBit); + } + + NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength); + + m_bitLength += numberOfBits; + } + + // + // 64 bit + // + [CLSCompliant(false)] + public void Write(UInt64 source) + { + EnsureBufferSize(m_bitLength + 64); + NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength); + m_bitLength += 64; + } + + [CLSCompliant(false)] + public void Write(UInt64 source, int numberOfBits) + { + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + public void Write(Int64 source) + { + EnsureBufferSize(m_bitLength + 64); + ulong usource = (ulong)source; + NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength); + m_bitLength += 64; + } + + public void Write(Int64 source, int numberOfBits) + { + EnsureBufferSize(m_bitLength + numberOfBits); + ulong usource = (ulong)source; + NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + // + // Floating point + // +#if UNSAFE + public unsafe void Write(float source) + { + uint val = *((uint*)&source); +#if BIGENDIAN + val = NetUtility.SwapByteOrder(val); +#endif + Write(val); + } +#else + public void Write(float source) + { + byte[] val = BitConverter.GetBytes(source); +#if BIGENDIAN + // swap byte order + byte tmp = val[3]; + val[3] = val[0]; + val[0] = tmp; + tmp = val[2]; + val[2] = val[1]; + val[1] = tmp; +#endif + Write(val); + } +#endif + +#if UNSAFE + public unsafe void Write(double source) + { + ulong val = *((ulong*)&source); +#if BIGENDIAN + val = NetUtility.SwapByteOrder(val); +#endif + Write(val); + } +#else + public void Write(double source) + { + byte[] val = BitConverter.GetBytes(source); +#if BIGENDIAN + // 0 1 2 3 4 5 6 7 + + // swap byte order + byte tmp = val[7]; + val[7] = val[0]; + val[0] = tmp; + + tmp = val[6]; + val[6] = val[1]; + val[1] = tmp; + + tmp = val[5]; + val[5] = val[2]; + val[2] = tmp; + + tmp = val[4]; + val[4] = val[3]; + val[3] = tmp; +#endif + Write(val); + } +#endif + + // + // Variable bits + // + + /// + /// Write Base128 encoded variable sized unsigned integer + /// + /// number of bytes written + [CLSCompliant(false)] + public int WriteVariableUInt32(uint value) + { + int retval = 1; + uint num1 = (uint)value; + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Write Base128 encoded variable sized signed integer + /// + /// number of bytes written + public int WriteVariableInt32(int value) + { + int retval = 1; + uint num1 = (uint)((value << 1) ^ (value >> 31)); + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Write Base128 encoded variable sized unsigned integer + /// + /// number of bytes written + [CLSCompliant(false)] + public int WriteVariableUInt64(UInt64 value) + { + int retval = 1; + UInt64 num1 = (UInt64)value; + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Compress (lossy) a float in the range -1..1 using numberOfBits bits + /// + public void WriteSignedSingle(float value, int numberOfBits) + { + NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value); + + float unit = (value + 1.0f) * 0.5f; + int maxVal = (1 << numberOfBits) - 1; + uint writeVal = (uint)(unit * (float)maxVal); + + Write(writeVal, numberOfBits); + } + + /// + /// Compress (lossy) a float in the range 0..1 using numberOfBits bits + /// + public void WriteUnitSingle(float value, int numberOfBits) + { + NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value); + + int maxValue = (1 << numberOfBits) - 1; + uint writeVal = (uint)(value * (float)maxValue); + + Write(writeVal, numberOfBits); + } + + /// + /// Compress a float within a specified range using a certain number of bits + /// + public void WriteRangedSingle(float value, float min, float max, int numberOfBits) + { + NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value); + + float range = max - min; + float unit = ((value - min) / range); + int maxVal = (1 << numberOfBits) - 1; + Write((UInt32)((float)maxVal * unit), numberOfBits); + } + + /// + /// Writes an integer with the least amount of bits need for the specified range + /// Returns number of bits written + /// + public int WriteRangedInteger(int min, int max, int value) + { + NetException.Assert(value >= min && value <= max, "Value not within min/max range!"); + + uint range = (uint)(max - min); + int numBits = NetUtility.BitsToHoldUInt(range); + + uint rvalue = (uint)(value - min); + Write(rvalue, numBits); + + return numBits; + } + + /// + /// Write a string + /// + public void Write(string source) + { + if (string.IsNullOrEmpty(source)) + { + EnsureBufferSize(m_bitLength + 8); + WriteVariableUInt32(0); + return; + } + + byte[] bytes = Encoding.UTF8.GetBytes(source); + EnsureBufferSize(m_bitLength + 1 + bytes.Length); + WriteVariableUInt32((uint)bytes.Length); + Write(bytes); + } + + /// + /// Writes an endpoint description + /// + /// + internal void Write(IPEndPoint endPoint) + { + byte[] bytes = endPoint.Address.GetAddressBytes(); + Write((byte)bytes.Length); + Write(bytes); + Write((ushort)endPoint.Port); + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + public void WritePadBits() + { + m_bitLength = ((m_bitLength + 7) >> 3) * 8; + EnsureBufferSize(m_bitLength); + } + + /// + /// Pads data with the specified number of bits. + /// + public void WritePadBits(int numberOfBits) + { + m_bitLength += numberOfBits; + EnsureBufferSize(m_bitLength); + } + + /// + /// Writes all public and private declared instance fields of the object in declaration order using reflection + /// + public void WriteAllFields(object ob) + { + WriteAllFields(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Writes all fields with specified binding in declaration order using reflection + /// + public void WriteAllFields(object ob, BindingFlags flags) + { + if (ob == null) + return; + Type tp = ob.GetType(); + + FieldInfo[] fields = tp.GetFields(flags); + foreach (FieldInfo fi in fields) + { + object value = fi.GetValue(ob); + + // find the appropriate Write method + MethodInfo writeMethod; + if (s_writeMethods.TryGetValue(fi.FieldType, out writeMethod)) + writeMethod.Invoke(this, new object[] { value }); + else + throw new NetException("Failed to find write method for type " + fi.FieldType); + } + } + + /// + /// Writes all public and private declared instance properties of the object in declaration order using reflection + /// + public void WriteAllProperties(object ob) + { + WriteAllProperties(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Writes all properties with specified binding in declaration order using reflection + /// + public void WriteAllProperties(object ob, BindingFlags flags) + { + if (ob == null) + return; + Type tp = ob.GetType(); + + PropertyInfo[] fields = tp.GetProperties(flags); + foreach (PropertyInfo fi in fields) + { + MethodInfo getMethod = fi.GetGetMethod((flags & BindingFlags.NonPublic) == BindingFlags.NonPublic); + object value = getMethod.Invoke(ob, null); + + // find the appropriate Write method + MethodInfo writeMethod; + if (s_writeMethods.TryGetValue(fi.PropertyType, out writeMethod)) + writeMethod.Invoke(this, new object[] { value }); + } + } + + /// + /// Append all the bits of message to this message + /// + public void Write(NetOutgoingMessage message) + { + EnsureBufferSize(m_bitLength + (message.LengthBytes * 8)); + + Write(message.m_data, 0, message.LengthBytes); + + // did we write excessive bits? + int bitsInLastByte = (message.m_bitLength % 8); + if (bitsInLastByte != 0) + { + int excessBits = 8 - bitsInLastByte; + m_bitLength -= excessBits; + } + } + } +} diff --git a/Lidgren.Network/NetOutgoingMessage.cs b/Lidgren.Network/NetOutgoingMessage.cs new file mode 100644 index 0000000..01ca175 --- /dev/null +++ b/Lidgren.Network/NetOutgoingMessage.cs @@ -0,0 +1,182 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Text; + +namespace Lidgren.Network +{ + [DebuggerDisplay("LengthBits={LengthBits}")] + public sealed partial class NetOutgoingMessage + { + // reference count before message can be recycled + internal int m_inQueueCount; + + internal NetMessageType m_type; + internal NetMessageLibraryType m_libType; + internal ushort m_sequenceNumber; + + internal IPEndPoint m_unconnectedRecipient; + + internal double m_lastSentTime; // when was this message sent last? + internal double m_nextResendTime; // when to resend this message the next time + internal int m_numSends; // the number of times this message has been sent/resent + + internal int m_fragmentGroupId; + internal int m_fragmentNumber; + internal int m_fragmentTotalCount; + + /// + /// Returns true if this message has been passed to SendMessage() already + /// + public bool IsSent { get { return m_numSends > 0; } } + + internal NetOutgoingMessage() + { + Reset(); + } + + internal void Reset() + { + NetException.Assert(m_inQueueCount == 0, "Ouch! Resetting NetOutgoingMessage still in some queue!"); + + m_bitLength = 0; + m_type = NetMessageType.Error; + m_inQueueCount = 0; + m_numSends = 0; + m_fragmentGroupId = -1; + } + + internal static int EncodeAcksMessage(byte[] buffer, int ptr, NetConnection conn, int maxBytesPayload) + { + // TODO: if appropriate; make bit vector of adjacent acks + + buffer[ptr++] = (byte)NetMessageType.Library; + buffer[ptr++] = (byte)NetMessageLibraryType.Acknowledge; + + Queue acks = conn.m_acknowledgesToSend; + + int maxAcks = maxBytesPayload / 3; + int acksToEncode = (acks.Count < maxAcks ? acks.Count : maxAcks); + + int payloadBitsLength = acksToEncode * 3 * 8; + if (payloadBitsLength < 127) + { + buffer[ptr++] = (byte)payloadBitsLength; + } + else + { + buffer[ptr++] = (byte)((payloadBitsLength & 127) | 128); + buffer[ptr++] = (byte)(payloadBitsLength >> 7); + } + + for (int i = 0; i < acksToEncode; i++) + { + int ack = acks.Dequeue(); + buffer[ptr++] = (byte)ack; // message type + buffer[ptr++] = (byte)(ack >> 8); // seqnr low + buffer[ptr++] = (byte)(ack >> 16); // seqnr high + } + + return ptr; + } + + internal int Encode(byte[] buffer, int ptr, NetConnection conn) + { + // message type + buffer[ptr++] = (byte)((int)m_type | (m_fragmentGroupId == -1 ? 0 : 128)); + + if (m_type == NetMessageType.Library) + buffer[ptr++] =(byte)m_libType; + + // channel sequence number + if (m_type >= NetMessageType.UserSequenced) + { + if (conn == null) + throw new NetException("Trying to encode NetMessageType " + m_type + " to unconnected endpoint!"); + if (m_numSends == 0) + m_sequenceNumber = conn.GetSendSequenceNumber(m_type); + buffer[ptr++] = (byte)m_sequenceNumber; + buffer[ptr++] = (byte)(m_sequenceNumber >> 8); + } + + // payload length + int payloadBitsLength = LengthBits; + int payloadBytesLength = NetUtility.BytesToHoldBits(payloadBitsLength); + if (payloadBitsLength < 127) + { + buffer[ptr++] = (byte)payloadBitsLength; + } + else if (payloadBitsLength < 32768) + { + buffer[ptr++] = (byte)((payloadBitsLength & 127) | 128); + buffer[ptr++] = (byte)(payloadBitsLength >> 7); + } + else + { + throw new NetException("Packet content too large; 4095 bytes maximum"); + } + + // fragmentation info + if (m_fragmentGroupId != -1) + { + buffer[ptr++] = (byte)m_fragmentGroupId; + buffer[ptr++] = (byte)(m_fragmentGroupId >> 8); + buffer[ptr++] = (byte)m_fragmentTotalCount; + buffer[ptr++] = (byte)(m_fragmentTotalCount >> 8); + buffer[ptr++] = (byte)m_fragmentNumber; + buffer[ptr++] = (byte)(m_fragmentNumber >> 8); + } + + // payload + if (payloadBitsLength > 0) + { + // zero out last byte + buffer[ptr + payloadBytesLength] = 0; + + Buffer.BlockCopy(m_data, 0, buffer, ptr, payloadBytesLength); + ptr += payloadBytesLength; + } + + m_numSends++; + + return ptr; + } + + public override string ToString() + { + StringBuilder bdr = new StringBuilder(); + bdr.Append("[NetOutgoingMessage "); + bdr.Append(m_type.ToString()); + if (m_type == NetMessageType.Library) + { + bdr.Append('|'); + bdr.Append(m_libType.ToString()); + } + bdr.Append(" #"); + bdr.Append(m_sequenceNumber); + bdr.Append(" sent "); + bdr.Append(m_numSends); + bdr.Append(" times]"); + return bdr.ToString(); + } + } +} diff --git a/Lidgren.Network/NetPeer.ConnectionApproval.cs b/Lidgren.Network/NetPeer.ConnectionApproval.cs new file mode 100644 index 0000000..1e7d9a4 --- /dev/null +++ b/Lidgren.Network/NetPeer.ConnectionApproval.cs @@ -0,0 +1,85 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System.Collections.Generic; +using System; + +namespace Lidgren.Network +{ + internal enum PendingConnectionStatus + { + NotPending = 0, + Pending, + Approved, + Denied, + } + + public partial class NetPeer + { + private List m_pendingConnections; + + private void AddPendingConnection(NetConnection conn, NetIncomingMessage approval) + { + if (m_pendingConnections == null) + m_pendingConnections = new List(); + m_pendingConnections.Add(conn); + conn.m_pendingStatus = PendingConnectionStatus.Pending; + + if (approval == null) + approval = CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, 0); + approval.m_messageType = NetMessageType.Library; + approval.m_senderConnection = conn; + approval.m_senderEndpoint = conn.m_remoteEndpoint; + + ReleaseMessage(approval); + } + + private void CheckPendingConnections() + { + if (m_pendingConnections == null || m_pendingConnections.Count < 1) + return; + + foreach (NetConnection conn in m_pendingConnections) + { + switch (conn.m_pendingStatus) + { + case PendingConnectionStatus.Pending: + if (NetTime.Now > conn.m_connectInitationTime + 10.0) + { + LogWarning("Pending connection still in pending state after 10 seconds; forgot to Approve/Deny?"); + m_pendingConnections.Remove(conn); + return; + } + break; + case PendingConnectionStatus.Approved: + // accept connection + AcceptConnection(conn); + m_pendingConnections.Remove(conn); + return; + case PendingConnectionStatus.Denied: + // send disconnected + NetOutgoingMessage bye = CreateLibraryMessage(NetMessageLibraryType.Disconnect, conn.m_pendingDenialReason); + EnqueueUnconnectedMessage(bye, conn.m_remoteEndpoint); + m_pendingConnections.Remove(conn); + return; + } + } + } + } +} diff --git a/Lidgren.Network/NetPeer.Discovery.cs b/Lidgren.Network/NetPeer.Discovery.cs new file mode 100644 index 0000000..b788209 --- /dev/null +++ b/Lidgren.Network/NetPeer.Discovery.cs @@ -0,0 +1,38 @@ +using System; +using System.Net; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + /// + /// Emit a discovery signal to all hosts on your subnet + /// + public void DiscoverLocalPeers(int serverPort) + { + NetOutgoingMessage om = CreateMessage(); + SendUnconnectedLibraryMessage(om, NetMessageLibraryType.Discovery, new IPEndPoint(IPAddress.Broadcast, serverPort)); + } + + /// + /// Emit a discovery signal to a single known host + /// + public bool DiscoverKnownPeer(string host, int serverPort) + { + IPAddress address = NetUtility.Resolve(host); + if (address == null) + return false; + return DiscoverKnownPeer(new IPEndPoint(address, serverPort)); + } + + /// + /// Emit a discovery signal to a single known host + /// + public bool DiscoverKnownPeer(IPEndPoint endpoint) + { + NetOutgoingMessage om = CreateMessage(); + SendUnconnectedLibraryMessage(om, NetMessageLibraryType.Discovery, endpoint); + return true; + } + } +} diff --git a/Lidgren.Network/NetPeer.Internal.cs b/Lidgren.Network/NetPeer.Internal.cs new file mode 100644 index 0000000..f960f6a --- /dev/null +++ b/Lidgren.Network/NetPeer.Internal.cs @@ -0,0 +1,603 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Net.NetworkInformation; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + private EndPoint m_senderRemote; + internal byte[] m_receiveBuffer; + internal byte[] m_sendBuffer; + internal Socket m_socket; + internal byte[] m_macAddressBytes; + private int m_listenPort; + private AutoResetEvent m_messageReceivedEvent; + + private NetQueue m_releasedIncomingMessages; + private NetQueue m_unsentUnconnectedMessage; + + /// + /// Signalling event which can be waited on to determine when a message is queued for reading. + /// Note that there is no guarantee that after the event is signaled the blocked thread will + /// find the message in the queue. Other user created threads could be preempted and dequeue + /// the message before the waiting thread wakes up. + /// + public AutoResetEvent MessageReceivedEvent { get { return m_messageReceivedEvent; } } + + private void InternalInitialize() + { + m_releasedIncomingMessages = new NetQueue(16); + m_unsentUnconnectedMessage = new NetQueue(4); + m_messageReceivedEvent = new AutoResetEvent(false); + } + + internal void ReleaseMessage(NetIncomingMessage msg) + { + NetException.Assert(msg.m_status != NetIncomingMessageReleaseStatus.ReleasedToApplication, "Message released to application twice!"); + + msg.m_status = NetIncomingMessageReleaseStatus.ReleasedToApplication; + m_releasedIncomingMessages.Enqueue(msg); + if (m_messageReceivedEvent != null) + m_messageReceivedEvent.Set(); + } + + [System.Diagnostics.Conditional("DEBUG")] + internal void VerifyNetworkThread() + { + Thread ct = System.Threading.Thread.CurrentThread; + if (ct != m_networkThread) + throw new NetException("Executing on wrong thread! Should be library system thread (is " + ct.Name + " mId " + ct.ManagedThreadId + ")"); + } + + // + // Network loop + // + private void Run() + { + // + // Initialize + // + VerifyNetworkThread(); + + InitializeRecycling(); + + System.Net.NetworkInformation.PhysicalAddress pa = NetUtility.GetMacAddress(); + if (pa != null) + { + m_macAddressBytes = pa.GetAddressBytes(); + LogVerbose("Mac address is " + NetUtility.ToHexString(m_macAddressBytes)); + } + else + { + LogWarning("Failed to get Mac address"); + } + + LogDebug("Network thread started"); + + lock (m_initializeLock) + { + if (m_status == NetPeerStatus.Running) + return; + + m_statistics.Reset(); + + // bind to socket + IPEndPoint iep = null; + try + { + iep = new IPEndPoint(m_configuration.LocalAddress, m_configuration.Port); + EndPoint ep = (EndPoint)iep; + + m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + m_socket.ReceiveBufferSize = m_configuration.ReceiveBufferSize; + m_socket.SendBufferSize = m_configuration.SendBufferSize; + m_socket.Blocking = false; + m_socket.Bind(ep); + + IPEndPoint boundEp = m_socket.LocalEndPoint as IPEndPoint; + LogDebug("Socket bound to " + boundEp + ": " + m_socket.IsBound); + + m_listenPort = boundEp.Port; + + long first = (pa == null ? (long)0 : (long)pa.GetHashCode()); + long second = (long)((long)boundEp.GetHashCode() << 32); + m_uniqueIdentifier = first ^ second; + + m_receiveBuffer = new byte[m_configuration.ReceiveBufferSize]; + m_sendBuffer = new byte[m_configuration.SendBufferSize]; + + LogVerbose("Initialization done"); + + // only set Running if everything succeeds + m_status = NetPeerStatus.Running; + } + catch (SocketException sex) + { + if (sex.SocketErrorCode == SocketError.AddressAlreadyInUse) + throw new NetException("Failed to bind to port " + (iep == null ? "Null" : iep.ToString()) + " - Address already in use!", sex); + throw; + } + catch (Exception ex) + { + throw new NetException("Failed to bind to " + (iep == null ? "Null" : iep.ToString()), ex); + } + } + + // + // Network loop + // + do + { + try + { + Heartbeat(); + } + catch (Exception ex) + { + LogWarning(ex.ToString()); + } + } while (m_status == NetPeerStatus.Running); + + // + // perform shutdown + // + LogDebug("Shutting down..."); + + // disconnect and make one final heartbeat + lock (m_connections) + { + foreach (NetConnection conn in m_connections) + if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Connecting) + conn.Disconnect(m_shutdownReason); + } + + // one final heartbeat, will send stuff and do disconnect + Heartbeat(); + + lock (m_initializeLock) + { + try + { + if (m_socket != null) + { + m_socket.Shutdown(SocketShutdown.Receive); + m_socket.Close(2); // 2 seconds timeout + } + if (m_messageReceivedEvent != null) + m_messageReceivedEvent.Close(); + } + finally + { + m_socket = null; + m_messageReceivedEvent = null; + m_status = NetPeerStatus.NotRunning; + LogDebug("Shutdown complete"); + } + } + + return; + } + + private void Heartbeat() + { + VerifyNetworkThread(); + +#if DEBUG + // send delayed packets + SendDelayedPackets(); +#endif + + // connection approval + CheckPendingConnections(); + + double now = NetTime.Now; + + // do connection heartbeats + foreach (NetConnection conn in m_connections) + { + conn.Heartbeat(now); + if (conn.m_status == NetConnectionStatus.Disconnected) + { + RemoveConnection(conn); + break; // can't continue iteration here + } + } + + // send unconnected sends + NetOutgoingMessage um; + while ((um = m_unsentUnconnectedMessage.TryDequeue()) != null) + { + IPEndPoint recipient = um.m_unconnectedRecipient; + + // + // TODO: use throttling here + // + + int ptr = um.Encode(m_sendBuffer, 0, null); + + if (recipient.Address.Equals(IPAddress.Broadcast)) + { + // send using broadcast + try + { + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + SendPacket(ptr, recipient, 1); + } + finally + { + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false); + } + } + else + { + // send normally + SendPacket(ptr, recipient, 1); + } + } + + // check if we need to reduce the recycled pool + ReduceStoragePool(); + + // + // read from socket + // + do + { + if (m_socket == null) + return; + + if (!m_socket.Poll(1000, SelectMode.SelectRead)) // wait up to 1 ms for data to arrive + return; + + //if (m_socket == null || m_socket.Available < 1) + // return; + + int bytesReceived = 0; + try + { + bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote); + } + catch (SocketException sx) + { + // no good response to this yet + if (sx.ErrorCode == 10054) + { + // connection reset by peer, aka forcibly closed + // we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?! + //LogWarning("Connection reset by peer, seemingly from " + m_senderRemote); + return; + } + + LogWarning(sx.ToString()); + return; + } + + if (bytesReceived < 1) + return; + + // renew current time; we might have waited in Poll + now = NetTime.Now; + + //LogVerbose("Received " + bytesReceived + " bytes"); + + IPEndPoint ipsender = (IPEndPoint)m_senderRemote; + + NetConnection sender = null; + m_connectionLookup.TryGetValue(ipsender, out sender); + + int ptr = 0; + NetMessageType msgType; + NetMessageLibraryType libType = NetMessageLibraryType.Error; + + // + // parse packet into messages + // + int numMessagesReceived = 0; + while ((bytesReceived - ptr) >= NetPeer.kMinPacketHeaderSize) + { + // get NetMessageType + byte top = m_receiveBuffer[ptr++]; + bool isFragment = (top & 128) == 128; + msgType = (NetMessageType)(top & 127); + + // get NetmessageLibraryType? + if (msgType == NetMessageType.Library) + libType = (NetMessageLibraryType)m_receiveBuffer[ptr++]; + + // get sequence number? + ushort sequenceNumber; + if (msgType >= NetMessageType.UserSequenced) + sequenceNumber = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8)); + else + sequenceNumber = 0; + + // get payload length + int payloadLengthBits = (int)m_receiveBuffer[ptr++]; + if ((payloadLengthBits & 128) == 128) // large payload + payloadLengthBits = (payloadLengthBits & 127) | (m_receiveBuffer[ptr++] << 7); + + int payloadLengthBytes = NetUtility.BytesToHoldBits(payloadLengthBits); + + if ((ptr + payloadLengthBytes) > bytesReceived) + { + LogWarning("Malformed message from " + ipsender.ToString() + "; not enough bytes"); + break; + } + + // + // handle incoming message + // + + if (msgType == NetMessageType.Error) + { + LogError("Malformed message; no message type!"); + continue; + } + + numMessagesReceived++; + + if (msgType == NetMessageType.Library) + { + if (sender == null) + HandleUnconnectedLibraryMessage(libType, ptr, payloadLengthBits, ipsender); + else + sender.HandleLibraryMessage(now, libType, ptr, payloadLengthBits); + } + else + { + if (sender == null) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData)) + HandleUnconnectedUserMessage(ptr, payloadLengthBits, ipsender); + } + else + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.Data)) + sender.HandleUserMessage(now, msgType, isFragment, sequenceNumber, ptr, payloadLengthBits); + } + } + + if (isFragment) + ptr += NetConstants.FragmentHeaderSize; + + ptr += payloadLengthBytes; + } + + m_statistics.PacketReceived(bytesReceived, numMessagesReceived); + + if (sender != null) + { + sender.m_lastHeardFrom = now; + sender.m_statistics.PacketReceived(bytesReceived, numMessagesReceived); + } + + if (ptr < bytesReceived) + { + // malformed packet + LogWarning("Malformed packet from " + sender + " (" + ipsender + "); " + (ptr - bytesReceived) + " stray bytes"); + continue; + } + } while (true); + + // heartbeat done + return; + } + + private void HandleUnconnectedLibraryMessage(NetMessageLibraryType libType, int ptr, int payloadLengthBits, IPEndPoint senderEndpoint) + { + VerifyNetworkThread(); + + if (libType != NetMessageLibraryType.Connect && libType != NetMessageLibraryType.Discovery && libType != NetMessageLibraryType.DiscoveryResponse) + { + LogWarning("Received unconnected library message of type " + libType); + return; + } + + int payloadLengthBytes = NetUtility.BytesToHoldBits(payloadLengthBits); + + // + // Handle Discovery + // + if (libType == NetMessageLibraryType.Discovery) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryRequest)) + { + NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadLengthBytes); + if (payloadLengthBytes > 0) + Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadLengthBytes); + dm.m_bitLength = payloadLengthBits; + dm.m_senderEndpoint = senderEndpoint; + ReleaseMessage(dm); + } + return; + } + + if (libType == NetMessageLibraryType.DiscoveryResponse) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryResponse)) + { + NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadLengthBytes); + if (payloadLengthBytes > 0) + Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadLengthBytes); + dr.m_bitLength = payloadLengthBits; + dr.m_senderEndpoint = senderEndpoint; + ReleaseMessage(dr); + } + return; + } + + // + // Handle NetMessageLibraryType.Connect + // + + if (!m_configuration.m_acceptIncomingConnections) + { + LogWarning("Connect received; but we're not accepting incoming connections!"); + return; + } + + string appIdent; + long remoteUniqueIdentifier = 0; + NetIncomingMessage approval = null; + try + { + NetIncomingMessage reader = new NetIncomingMessage(); + + reader.m_data = GetStorage(payloadLengthBytes); + Buffer.BlockCopy(m_receiveBuffer, ptr, reader.m_data, 0, payloadLengthBytes); + ptr += payloadLengthBytes; + reader.m_bitLength = payloadLengthBits; + appIdent = reader.ReadString(); + remoteUniqueIdentifier = reader.ReadInt64(); + + int approvalBitLength = (int)reader.ReadVariableUInt32(); + if (approvalBitLength > 0) + { + int approvalByteLength = NetUtility.BytesToHoldBits(approvalBitLength); + if (approvalByteLength < m_configuration.MaximumTransmissionUnit) + { + approval = CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, approvalByteLength); + reader.ReadBits(approval.m_data, 0, approvalBitLength); + approval.m_bitLength = approvalBitLength; + } + } + } + catch (Exception ex) + { + // malformed connect packet + LogWarning("Malformed connect packet from " + senderEndpoint + " - " + ex.ToString()); + return; + } + + if (appIdent.Equals(m_configuration.AppIdentifier) == false) + { + // wrong app ident + LogWarning("Connect received with wrong appidentifier (need '" + m_configuration.AppIdentifier + "' found '" + appIdent + "') from " + senderEndpoint); + return; + } + + // ok, someone wants to connect to us, and we're accepting connections! + if (m_connections.Count >= m_configuration.MaximumConnections) + { + HandleServerFull(senderEndpoint); + return; + } + + NetConnection conn = new NetConnection(this, senderEndpoint); + conn.m_connectionInitiator = false; + conn.m_connectInitationTime = NetTime.Now; + conn.m_remoteUniqueIdentifier = remoteUniqueIdentifier; + + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval)) + { + // do connection approval before accepting this connection + AddPendingConnection(conn, approval); + return; + } + + AcceptConnection(conn); + return; + } + + private void HandleUnconnectedUserMessage(int ptr, int payloadLengthBits, IPEndPoint senderEndpoint) + { + VerifyNetworkThread(); + + NetIncomingMessage ium = CreateIncomingMessage(NetIncomingMessageType.UnconnectedData, m_receiveBuffer, ptr, NetUtility.BytesToHoldBits(payloadLengthBits)); + ium.m_bitLength = payloadLengthBits; + ium.m_senderEndpoint = senderEndpoint; + ReleaseMessage(ium); + } + + private void AcceptConnection(NetConnection conn) + { + lock (m_connections) + { + m_connections.Add(conn); + m_connectionLookup[conn.m_remoteEndpoint] = conn; + } + conn.SetStatus(NetConnectionStatus.Connecting, "Connecting"); + + // send connection response + conn.SendConnectResponse(); + + conn.m_connectInitationTime = NetTime.Now; + + return; + } + + internal void RemoveConnection(NetConnection conn) + { + lock (m_connections) + { + m_connections.Remove(conn); + m_connectionLookup.Remove(conn.m_remoteEndpoint); + } + conn.Dispose(); + } + + private void HandleServerFull(IPEndPoint connecter) + { + const string rejectMessage = "Server is full!"; + NetOutgoingMessage reply = CreateLibraryMessage(NetMessageLibraryType.Disconnect, rejectMessage); + EnqueueUnconnectedMessage(reply, connecter); + } + + // called by user and network thread + private void EnqueueUnconnectedMessage(NetOutgoingMessage msg, IPEndPoint recipient) + { + msg.m_unconnectedRecipient = recipient; + Interlocked.Increment(ref msg.m_inQueueCount); + m_unsentUnconnectedMessage.Enqueue(msg); + } + + internal static NetDeliveryMethod GetDeliveryMethod(NetMessageType mtp) + { + if (mtp >= NetMessageType.UserReliableOrdered) + return NetDeliveryMethod.ReliableOrdered; + else if (mtp >= NetMessageType.UserReliableSequenced) + return NetDeliveryMethod.ReliableSequenced; + else if (mtp >= NetMessageType.UserReliableUnordered) + return NetDeliveryMethod.ReliableUnordered; + else if (mtp >= NetMessageType.UserSequenced) + return NetDeliveryMethod.UnreliableSequenced; + return NetDeliveryMethod.Unreliable; + } + + internal void SendImmediately(NetConnection conn, NetOutgoingMessage msg) + { + NetException.Assert(msg.m_type == NetMessageType.Library, "SendImmediately can only send library (non-reliable) messages"); + + msg.m_inQueueCount = 1; + int len = msg.Encode(m_sendBuffer, 0, conn); + Interlocked.Decrement(ref msg.m_inQueueCount); + + SendPacket(len, conn.m_remoteEndpoint, 1); + + Recycle(msg); + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetPeer.LatencySimulation.cs b/Lidgren.Network/NetPeer.LatencySimulation.cs new file mode 100644 index 0000000..60f2824 --- /dev/null +++ b/Lidgren.Network/NetPeer.LatencySimulation.cs @@ -0,0 +1,139 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + +#if DEBUG + private List m_delayedPackets = new List(); + + private class DelayedPacket + { + public byte[] Data; + public double DelayedUntil; + public IPEndPoint Target; + } + + internal void SendPacket(int numBytes, IPEndPoint target, int numMessages) + { + // simulate loss + float loss = m_configuration.m_loss; + if (loss > 0.0f) + { + if (NetRandom.Instance.Chance(m_configuration.m_loss)) + { + LogVerbose("Sending packet " + numBytes + " bytes - SIMULATED LOST!"); + return; // packet "lost" + } + } + + m_statistics.PacketSent(numBytes, numMessages); + + // simulate latency + float m = m_configuration.m_minimumOneWayLatency; + float r = m_configuration.m_randomOneWayLatency; + if (m == 0.0f && r == 0.0f) + { + // no latency simulation + LogVerbose("Sending packet " + numBytes + " bytes"); + ActuallySendPacket(m_sendBuffer, numBytes, target); + return; + } + + int num = 1; + if (m_configuration.m_duplicates > 0.0f && NetRandom.Instance.Chance(m_configuration.m_duplicates)) + num++; + + float delay = 0; + for (int i = 0; i < num; i++) + { + delay = m_configuration.m_minimumOneWayLatency + (NetRandom.Instance.NextFloat() * m_configuration.m_randomOneWayLatency); + + // Enqueue delayed packet + DelayedPacket p = new DelayedPacket(); + p.Target = target; + p.Data = new byte[numBytes]; + Buffer.BlockCopy(m_sendBuffer, 0, p.Data, 0, numBytes); + p.DelayedUntil = NetTime.Now + delay; + + m_delayedPackets.Add(p); + } + + LogVerbose("Sending packet " + numBytes + " bytes - delayed " + NetTime.ToReadable(delay)); + } + + private void SendDelayedPackets() + { + if (m_delayedPackets.Count <= 0) + return; + + double now = NetTime.Now; + + RestartDelaySending: + foreach (DelayedPacket p in m_delayedPackets) + { + if (now > p.DelayedUntil) + { + ActuallySendPacket(p.Data, p.Data.Length, p.Target); + m_delayedPackets.Remove(p); + goto RestartDelaySending; + } + } + } + + internal void ActuallySendPacket(byte[] data, int numBytes, IPEndPoint target) + { + try + { + int bytesSent = m_socket.SendTo(data, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + } +#else + // + // Release - just send the packet straight away + // + internal void SendPacket(int numBytes, IPEndPoint target, int numMessages) + { + try + { + int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + } +#endif + } +} diff --git a/Lidgren.Network/NetPeer.Logging.cs b/Lidgren.Network/NetPeer.Logging.cs new file mode 100644 index 0000000..2eb0102 --- /dev/null +++ b/Lidgren.Network/NetPeer.Logging.cs @@ -0,0 +1,51 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System.Diagnostics; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + [Conditional("DEBUG")] + internal void LogVerbose(string message) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.VerboseDebugMessage, message)); + } + + [Conditional("DEBUG")] + internal void LogDebug(string message) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DebugMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.DebugMessage, message)); + } + + internal void LogWarning(string message) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.WarningMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.WarningMessage, message)); + } + + internal void LogError(string message) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ErrorMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.ErrorMessage, message)); + } + } +} diff --git a/Lidgren.Network/NetPeer.Recycling.cs b/Lidgren.Network/NetPeer.Recycling.cs new file mode 100644 index 0000000..890360d --- /dev/null +++ b/Lidgren.Network/NetPeer.Recycling.cs @@ -0,0 +1,280 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System.Collections.Generic; +using System; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + internal int m_storedBytes; + private int m_maxStoredBytes; + private List m_storagePool = new List(); + private NetQueue m_incomingMessagesPool = new NetQueue(16); + private NetQueue m_outgoingMessagesPool = new NetQueue(16); + + private void InitializeRecycling() + { + m_storagePool.Clear(); + m_storedBytes = 0; + m_maxStoredBytes = m_configuration.m_maxRecycledBytesKept; + m_incomingMessagesPool.Clear(); + m_outgoingMessagesPool.Clear(); + } + + internal byte[] GetStorage(int requiredBytes) + { + if (m_storagePool.Count < 1) + { + m_statistics.m_bytesAllocated += requiredBytes; + return new byte[requiredBytes]; + } + + lock (m_storagePool) + { + // search from end to start + for (int i = m_storagePool.Count - 1; i >= 0; i--) + { + byte[] retval = m_storagePool[i]; + if (retval.Length >= requiredBytes) + { + m_storagePool.RemoveAt(i); + m_storedBytes -= retval.Length; + + return retval; + } + } + } + + m_statistics.m_bytesAllocated += requiredBytes; + return new byte[requiredBytes]; + } + + /// + /// Creates a new message for sending + /// + public NetOutgoingMessage CreateMessage() + { + return CreateMessage(m_configuration.DefaultOutgoingMessageCapacity); + } + + /// + /// Creates a new message for sending + /// + /// initial capacity in bytes + public NetOutgoingMessage CreateMessage(int initialCapacity) + { + NetOutgoingMessage retval = m_outgoingMessagesPool.TryDequeue(); + if (retval == null) + retval = new NetOutgoingMessage(); + else + retval.Reset(); + + byte[] storage = GetStorage(initialCapacity); + retval.m_data = storage; + + return retval; + } + + internal NetOutgoingMessage CreateLibraryMessage(NetMessageLibraryType tp, string content) + { + NetOutgoingMessage retval = CreateMessage(1 + (content == null ? 0 : content.Length)); + retval.m_type = NetMessageType.Library; + retval.m_libType = tp; + retval.Write((content == null ? "" : content)); + return retval; + } + + /// + /// Recycle the message to the library for reuse + /// + public void Recycle(NetIncomingMessage msg) + { + if (msg.m_status != NetIncomingMessageReleaseStatus.ReleasedToApplication) + throw new NetException("Message not under application control; recycled more than once?"); + + msg.m_status = NetIncomingMessageReleaseStatus.RecycledByApplication; + if (msg.m_data != null) + { + lock (m_storagePool) + { +#if DEBUG + if (m_storagePool.Contains(msg.m_data)) + throw new NetException("Storage pool object recycled twice!"); +#endif + m_storedBytes += msg.m_data.Length; + m_storagePool.Add(msg.m_data); + } + msg.m_data = null; + } + m_incomingMessagesPool.Enqueue(msg); + } + + /// + /// Recycle the message to the library for reuse + /// + internal void Recycle(NetOutgoingMessage msg) + { + VerifyNetworkThread(); + +#if DEBUG + lock (m_connections) + { + foreach (NetConnection conn in m_connections) + { + if (conn.m_unsentMessages.Contains(msg)) + throw new NetException("Ouch! Recycling unsent message!"); + + for(int i=0;i list = conn.m_storedMessages[i]; + if (list != null && list.Count > 0) + { + foreach (NetOutgoingMessage om in conn.m_storedMessages[i]) + { + if (om == msg) + throw new NetException("Ouch! Recycling stored message!"); + } + } + } + } + } +#endif + NetException.Assert(msg.m_inQueueCount == 0, "Recycling message still in some queue!"); + + if (msg.m_data != null) + { + lock (m_storagePool) + { + if (!m_storagePool.Contains(msg.m_data)) + { + m_storedBytes += msg.m_data.Length; + m_storagePool.Add(msg.m_data); + } + } + msg.m_data = null; + } + m_outgoingMessagesPool.Enqueue(msg); + } + + /// + /// Call to check if storage pool should be reduced + /// + private void ReduceStoragePool() + { + VerifyNetworkThread(); + + if (m_storedBytes < m_configuration.m_maxRecycledBytesKept) + return; // never mind threading, no big deal if storage is larger than config setting for a frame + + int wasStoredBytes; + int reduceTo; + lock (m_storagePool) + { + // since newly stored message at added to the end; remove from the start + wasStoredBytes = m_storedBytes; + reduceTo = m_maxStoredBytes / 2; + + while (m_storedBytes > reduceTo && m_storagePool.Count > 0) + { + byte[] arr = m_storagePool[0]; + m_storedBytes -= arr.Length; + m_storagePool.RemoveAt(0); + } + } + + // done + LogDebug("Reduced recycled bytes pool from " + wasStoredBytes + " bytes to " + m_storedBytes + " bytes (target " + reduceTo + ")"); + + return; + } + + /// + /// Creates an incoming message with the required capacity for releasing to the application + /// + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, string contents) + { + NetIncomingMessage retval; + if (string.IsNullOrEmpty(contents)) + { + retval = CreateIncomingMessage(tp, 1); + retval.Write(""); + return retval; + } + + byte[] bytes = System.Text.Encoding.UTF8.GetBytes(contents); + retval = CreateIncomingMessage(tp, bytes.Length + (bytes.Length > 127 ? 2 : 1)); + retval.Write(contents); + + return retval; + } + + /// + /// Creates an incoming message with the required capacity for releasing to the application + /// + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, int requiredCapacity) + { + NetIncomingMessage retval = m_incomingMessagesPool.TryDequeue(); + if (retval == null) + retval = new NetIncomingMessage(); + else + retval.Reset(); + + NetException.Assert(retval.m_status != NetIncomingMessageReleaseStatus.ReleasedToApplication); + + retval.m_incomingType = tp; + retval.m_senderConnection = null; + retval.m_senderEndpoint = null; + retval.m_status = NetIncomingMessageReleaseStatus.NotReleased; + + if (requiredCapacity > 0) + { + byte[] storage = GetStorage(requiredCapacity); + retval.m_data = storage; + } + else + { + retval.m_data = null; + } + + return retval; + } + + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] copyFrom, int offset, int copyLength) + { + NetIncomingMessage retval = m_incomingMessagesPool.TryDequeue(); + if (retval == null) + retval = new NetIncomingMessage(); + else + retval.Reset(); + + retval.m_data = GetStorage(copyLength); + Buffer.BlockCopy(copyFrom, offset, retval.m_data, 0, copyLength); + + retval.m_bitLength = copyLength * 8; + retval.m_incomingType = tp; + retval.m_senderConnection = null; + retval.m_senderEndpoint = null; + + return retval; + } + + } +} diff --git a/Lidgren.Network/NetPeer.cs b/Lidgren.Network/NetPeer.cs new file mode 100644 index 0000000..4fde1e9 --- /dev/null +++ b/Lidgren.Network/NetPeer.cs @@ -0,0 +1,342 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Threading; + +namespace Lidgren.Network +{ + // + // This partial file holds public netpeer methods accessible to the application + // + [DebuggerDisplay("Status={m_status}")] + public partial class NetPeer + { + internal const int kMinPacketHeaderSize = 2; + internal const int kMaxPacketHeaderSize = 5; + + private NetPeerStatus m_status; + private object m_initializeLock = new object(); + internal long m_uniqueIdentifier; + + internal NetPeerConfiguration m_configuration; + internal NetPeerStatistics m_statistics; + private Thread m_networkThread; + private string m_shutdownReason; + + internal List m_connections; + private Dictionary m_connectionLookup; + + /// + /// Gets the status of the NetPeer + /// + public NetPeerStatus Status { get { return m_status; } } + + /// + /// Gets a copy of the list of connections + /// + public NetConnection[] Connections + { + get + { + lock (m_connections) + return m_connections.ToArray(); + } + } + + /// + /// Returns the number of active connections + /// + public int ConnectionsCount + { + get { return m_connections.Count; } + } + + /// + /// Statistics on this NetPeer since it was initialized + /// + public NetPeerStatistics Statistics + { + get { return m_statistics; } + } + + /// + /// Gets the configuration of the netpeer + /// + public NetPeerConfiguration Configuration { get { return m_configuration; } } + + /// + /// Gets the port number this NetPeer is listening and sending on + /// + public int Port { get { return m_listenPort; } } + + /// + /// Gets a semi-unique identifier based on Mac address and ip/port. Note! Not available until Start has been called! + /// + public long UniqueIdentifier { get { return m_uniqueIdentifier; } } + + public NetPeer(NetPeerConfiguration configuration) + { + m_status = NetPeerStatus.NotRunning; + m_configuration = configuration; + m_connections = new List(); + m_connectionLookup = new Dictionary(); + m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0); + m_statistics = new NetPeerStatistics(this); + + InternalInitialize(); + } + + /// + /// Binds to socket + /// + public void Start() + { + if (m_status != NetPeerStatus.NotRunning) + { + // already running! Just ignore... + LogWarning("Start() called on already running NetPeer - ignoring."); + return; + } + + m_status = NetPeerStatus.Starting; + + m_releasedIncomingMessages.Clear(); + m_unsentUnconnectedMessage.Clear(); + + m_configuration.VerifyAndLock(); + + // start network thread + m_networkThread = new Thread(new ThreadStart(Run)); + m_networkThread.Name = "Lidgren network thread"; + m_networkThread.IsBackground = true; + m_networkThread.Start(); + + // allow some time for network thread to start up in case they call Connect() immediately + Thread.Sleep(3); + } + + /// + /// Read a pending message from any connection, if any + /// + public NetIncomingMessage ReadMessage() + { + if (m_status == NetPeerStatus.NotRunning) + return null; + + return m_releasedIncomingMessages.TryDequeue(); + } + + public NetIncomingMessage WaitMessage(int maxMillis) + { + if (m_messageReceivedEvent != null) + m_messageReceivedEvent.WaitOne(maxMillis); + return m_releasedIncomingMessages.TryDequeue(); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(string host, int port) + { + return Connect(new IPEndPoint(NetUtility.Resolve(host), port), null); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(IPEndPoint remoteEndpoint) + { + return Connect(remoteEndpoint, null); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(string host, int port, NetOutgoingMessage approvalMessage) + { + return Connect(new IPEndPoint(NetUtility.Resolve(host), port), approvalMessage); + } + + /// + /// Create a connection to a remote endpoint + /// + public virtual NetConnection Connect(IPEndPoint remoteEndpoint, NetOutgoingMessage approvalMessage) + { + if (m_status == NetPeerStatus.NotRunning) + throw new NetException("Must call Start() first"); + + if (m_connectionLookup.ContainsKey(remoteEndpoint)) + throw new NetException("Already connected to that endpoint!"); + + NetConnection conn = new NetConnection(this, remoteEndpoint); + conn.m_approvalMessage = approvalMessage; + + // handle on network thread + conn.m_connectRequested = true; + conn.m_connectionInitiator = true; + + lock (m_connections) + { + m_connections.Add(conn); + m_connectionLookup[remoteEndpoint] = conn; + } + + return conn; + } + + /// + /// Send a message to an existing connection + /// + public bool SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod deliveryMethod) + { + return SendMessage(msg, recipient, deliveryMethod, 0); + } + + /// + /// Send a message to an existing connection + /// + public bool SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod deliveryMethod, int channel) + { + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + if (channel < 0 || channel > 63) + throw new NetException("Channel must be between 0 and 63"); + if (channel != 0 && (deliveryMethod == NetDeliveryMethod.Unreliable || deliveryMethod == NetDeliveryMethod.ReliableUnordered)) + throw new NetException("Channel must be 0 for Unreliable and ReliableUnordered"); + + if (m_status != NetPeerStatus.Running) + return false; + + msg.m_type = (NetMessageType)((int)deliveryMethod + channel); + + recipient.EnqueueOutgoingMessage(msg); + + return true; + } + + /// + /// Send a message to a number of existing connections + /// + public bool SendMessage(NetOutgoingMessage msg, IEnumerable recipients, NetDeliveryMethod deliveryMethod, int channel) + { + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + if (channel < 0 || channel > 63) + throw new NetException("Channel must be between 0 and 63"); + if (channel != 0 && (deliveryMethod == NetDeliveryMethod.Unreliable || deliveryMethod == NetDeliveryMethod.ReliableUnordered)) + throw new NetException("Channel must be 0 for Unreliable and ReliableUnordered"); + + if (m_status != NetPeerStatus.Running) + return false; + + msg.m_type = (NetMessageType)((int)deliveryMethod + channel); + + foreach (NetConnection conn in recipients) + conn.EnqueueOutgoingMessage(msg); + + return true; + } + + /// + /// Send a message to an unconnected host + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, string host, int port) + { + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + + IPAddress adr = NetUtility.Resolve(host); + if (adr == null) + throw new NetException("Failed to resolve " + host); + + msg.m_type = NetMessageType.UserUnreliable; // sortof not applicable + EnqueueUnconnectedMessage(msg, new IPEndPoint(adr, port)); + } + + /// + /// Send a message to an unconnected host + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, IPEndPoint recipient) + { + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + msg.m_type = NetMessageType.UserUnreliable; // sortof not applicable + EnqueueUnconnectedMessage(msg, recipient); + } + + internal void SendUnconnectedLibraryMessage(NetOutgoingMessage msg, NetMessageLibraryType libType, IPEndPoint recipient) + { + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + msg.m_type = NetMessageType.Library; + msg.m_libType = libType; + EnqueueUnconnectedMessage(msg, recipient); + } + + /// + /// Send a message to a number of unconnected hosts + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, IEnumerable recipients) + { + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + msg.m_type = NetMessageType.UserUnreliable; // sortof not applicable + foreach (IPEndPoint ipe in recipients) + EnqueueUnconnectedMessage(msg, ipe); + } + + /// + /// Send a discovery response message + /// + public void SendDiscoveryResponse(NetOutgoingMessage msg, IPEndPoint recipient) + { + if (msg == null) + msg = CreateMessage(0); + if (msg.IsSent) + throw new NetException("Message has already been sent!"); + msg.m_type = NetMessageType.Library; + msg.m_libType = NetMessageLibraryType.DiscoveryResponse; + EnqueueUnconnectedMessage(msg, recipient); + } + + /// + /// Disconnects all active connections and closes the socket + /// + public void Shutdown(string bye) + { + // called on user thread + + if (m_socket == null) + return; // already shut down + + LogDebug("Shutdown requested"); + m_shutdownReason = bye; + m_status = NetPeerStatus.ShutdownRequested; + } + + public override string ToString() + { + return "[NetPeer bound to " + m_socket.LocalEndPoint + " " + ConnectionsCount + " connections]"; + } + } +} diff --git a/Lidgren.Network/NetPeerConfiguration.cs b/Lidgren.Network/NetPeerConfiguration.cs new file mode 100644 index 0000000..37a1940 --- /dev/null +++ b/Lidgren.Network/NetPeerConfiguration.cs @@ -0,0 +1,442 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Net; + +namespace Lidgren.Network +{ + /// + /// Partly immutable after NetPeer has been initialized + /// + public sealed class NetPeerConfiguration + { + private const string c_isLockedMessage = "You may not alter the NetPeerConfiguration after the NetPeer has been initialized!"; + + private bool m_isLocked; + internal bool m_acceptIncomingConnections; + internal string m_appIdentifier; + internal IPAddress m_localAddress; + internal int m_port; + internal int m_receiveBufferSize, m_sendBufferSize; + internal int m_defaultOutgoingMessageCapacity; + internal int m_maximumTransmissionUnit; + internal int m_maximumConnections; + internal NetIncomingMessageType m_disabledTypes; + internal int m_throttleBytesPerSecond; + internal int m_throttlePeakBytes; + internal int m_maxRecycledBytesKept; + + // handshake, timeout and keepalive + internal float m_handshakeAttemptDelay; + internal int m_handshakeMaxAttempts; + internal float m_connectionTimeout; + internal float m_keepAliveDelay; + internal float m_pingFrequency; + + // reliability + internal float[] m_resendRTTMultiplier; + internal float[] m_resendBaseTime; + internal float m_maxAckDelayTime; + + // bad network simulation + internal float m_loss; + internal float m_duplicates; + internal float m_minimumOneWayLatency; + internal float m_randomOneWayLatency; + + public NetPeerConfiguration(string appIdentifier) + { + if (string.IsNullOrEmpty(appIdentifier)) + throw new NetException("App identifier must be at least one character long"); + m_appIdentifier = appIdentifier; + + // defaults + m_isLocked = false; + m_acceptIncomingConnections = true; + m_localAddress = IPAddress.Any; + m_port = 0; + m_receiveBufferSize = 131071; + m_sendBufferSize = 131071; + m_keepAliveDelay = 4.0f; + m_connectionTimeout = 25; + m_maximumConnections = 8; + m_defaultOutgoingMessageCapacity = 8; + m_pingFrequency = 6.0f; + m_throttleBytesPerSecond = 1024 * 512; + m_throttlePeakBytes = 8192; + m_maxAckDelayTime = 0.01f; + m_handshakeAttemptDelay = 1.0f; + m_handshakeMaxAttempts = 7; + m_maxRecycledBytesKept = 128 * 1024; + + m_loss = 0.0f; + m_minimumOneWayLatency = 0.0f; + m_randomOneWayLatency = 0.0f; + m_duplicates = 0.0f; + + // default disabled types + m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage; + + // reliability + m_resendRTTMultiplier = new float[] + { + 1.1f, + 2.25f, + 3.5f, + 4.0f, + 4.0f, + 4.0f, + 4.0f, + 4.0f, + 4.0f, + 6.0f, + 6.0f + }; + + m_resendBaseTime = new float[] + { + 0.025f, // just processing time + ack delay wait time + 0.05f, // just processing time + ack delay wait time + 0.2f, // 0.16 delay since last resend + 0.5f, // 0.3 delay + 1.5f, // 1.0 delay + 3.0f, // 1.5 delay + 5.0f, // 2.0 delay + 7.5f, // 2.5 delay + 12.5f, // 5.0 delay + 17.5f, // 5.0 delay + 25.0f // 7.5 delay, obi wan you're my only hope + }; + + // Maximum transmission unit + // The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468) + // 20 bytes ip header + // 8 bytes udp header + // 5 bytes lidgren header for one message + // 1 byte just to be on the safe side + // Totals 1440 minus 34 = 1406 bytes free for payload + m_maximumTransmissionUnit = 1406; + } + + public NetPeerConfiguration Clone() + { + NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration; + retval.m_isLocked = false; + return retval; + } + + internal void VerifyAndLock() + { + if (m_throttleBytesPerSecond < m_maximumTransmissionUnit) + m_throttleBytesPerSecond = m_maximumTransmissionUnit; + + m_isLocked = true; + } + +#if DEBUG + /// + /// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f + /// + public float SimulatedLoss + { + get { return m_loss; } + set { m_loss = value; } + } + + /// + /// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds + /// + public float SimulatedMinimumLatency + { + get { return m_minimumOneWayLatency; } + set { m_minimumOneWayLatency = value; } + } + + /// + /// Gets or sets the simulated added random amount of one way latency for sent packets in seconds + /// + public float SimulatedRandomLatency + { + get { return m_randomOneWayLatency; } + set { m_randomOneWayLatency = value; } + } + + /// + /// Gets the average simulated one way latency in seconds + /// + public float SimulatedAverageLatency + { + get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); } + } + + /// + /// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f + /// + public float SimulatedDuplicatesChance + { + get { return m_duplicates; } + set { m_duplicates = value; } + } + +#endif + + /// + /// Gets or sets the identifier of this application; the library can only connect to matching app identifier peers + /// + public string AppIdentifier + { + get { return m_appIdentifier; } + } + + /// + /// Enables receiving of the specified type of message + /// + public void EnableMessageType(NetIncomingMessageType tp) + { + m_disabledTypes &= (~tp); + } + + /// + /// Disables receiving of the specified type of message + /// + public void DisableMessageType(NetIncomingMessageType tp) + { + m_disabledTypes |= tp; + } + + /// + /// Enables or disables receiving of the specified type of message + /// + public void SetMessageTypeEnabled(NetIncomingMessageType tp, bool enabled) + { + if (enabled) + m_disabledTypes &= (~tp); + else + m_disabledTypes |= tp; + } + + /// + /// Gets if receiving of the specified type of message is enabled + /// + public bool IsMessageTypeEnabled(NetIncomingMessageType tp) + { + return !((m_disabledTypes & tp) == tp); + } + + /// + /// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers + /// + public int MaximumTransmissionUnit + { + get { return m_maximumTransmissionUnit; } + set + { + if (value < 1 || value >= 4096) + throw new NetException("MaximumTransmissionUnit must be between 1 and 4095 bytes"); + m_maximumTransmissionUnit = value; + } + } + + /// + /// Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized. + /// + public int MaximumConnections + { + get { return m_maximumConnections; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_maximumConnections = value; + } + } + + /// + /// Gets or sets if the NetPeer should accept incoming connections + /// + public bool AcceptIncomingConnections + { + get { return m_acceptIncomingConnections; } + set { m_acceptIncomingConnections = value; } + } + + /// + /// Gets or sets the default capacity in bytes when NetPeer.CreateMessage() is called without argument + /// + public int DefaultOutgoingMessageCapacity + { + get { return m_defaultOutgoingMessageCapacity; } + set { m_defaultOutgoingMessageCapacity = value; } + } + + /// + /// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized. + /// + public IPAddress LocalAddress + { + get { return m_localAddress; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_localAddress = value; + } + } + + /// + /// Gets or sets the local port to bind to. Defaults to 0. Cannot be changed once NetPeer is initialized. + /// + public int Port + { + get { return m_port; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_port = value; + } + } + + /// + /// Gets or sets the size in bytes of the receiving buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized. + /// + public int ReceiveBufferSize + { + get { return m_receiveBufferSize; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_receiveBufferSize = value; + } + } + + /// + /// Gets or sets the size in bytes of the sending buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized. + /// + public int SendBufferSize + { + get { return m_sendBufferSize; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_sendBufferSize = value; + } + } + + /// + /// Gets or sets the number of seconds of inactivity before sending an extra ping packet as keepalive. This should be shorter than ping interval. + /// + public float KeepAliveDelay + { + get { return m_keepAliveDelay; } + set + { + if (value < m_pingFrequency) + throw new NetException("Setting KeepAliveDelay to lower than ping frequency doesn't make sense!"); + m_keepAliveDelay = value; + } + } + + /// + /// Gets or sets the number of seconds of non-response before disconnecting because of time out. Cannot be changed once NetPeer is initialized. + /// + public float ConnectionTimeout + { + get { return m_connectionTimeout; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_connectionTimeout = value; + } + } + + /// + /// Gets or sets the number of seconds between latency calculation (rtt) pings + /// + public float PingFrequency + { + get { return m_pingFrequency; } + set { m_pingFrequency = value; } + } + + /// + /// Gets or sets the number of allowed bytes to be sent per second per connection; 0 means unlimited + /// + public int ThrottleBytesPerSecond + { + get { return m_throttleBytesPerSecond; } + set + { + m_throttleBytesPerSecond = value; + if (m_throttleBytesPerSecond < m_maximumTransmissionUnit) + throw new NetException("ThrottleBytesPerSecond can not be lower than MaximumTransmissionUnit"); + } + } + + /// + /// Gets or sets the peak number of bytes sent before throttling kicks in + /// + public int ThrottlePeakBytes + { + get { return m_throttlePeakBytes; } + set + { + m_throttlePeakBytes = value; + if (m_throttlePeakBytes < m_maximumTransmissionUnit) + throw new NetException("ThrottlePeakBytes can not be lower than MaximumTransmissionUnit"); + } + } + + /// + /// Gets or sets the number between handshake attempts in seconds + /// + public float HandshakeAttemptDelay + { + get { return m_handshakeAttemptDelay; } + set { m_handshakeAttemptDelay = value; } + } + + /// + /// Gets or sets the maximum number of handshake attempts before declaring failure to shake hands + /// + public int HandshakeMaxAttempts + { + get { return m_handshakeMaxAttempts; } + set { m_handshakeMaxAttempts = value; } + } + + /// + /// Gets or sets the maximum number of bytes kept in the recycle pool. Cannot be changed once NetPeer is initialized. + /// + public int MaxRecycledBytesKept + { + get { return m_maxRecycledBytesKept; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_maxRecycledBytesKept = value; + } + } + } +} diff --git a/Lidgren.Network/NetPeerStatistics.cs b/Lidgren.Network/NetPeerStatistics.cs new file mode 100644 index 0000000..efdad46 --- /dev/null +++ b/Lidgren.Network/NetPeerStatistics.cs @@ -0,0 +1,128 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + public sealed class NetPeerStatistics + { + private NetPeer m_peer; + + internal int m_sentPackets; + internal int m_receivedPackets; + + internal int m_sentMessages; + internal int m_receivedMessages; + + internal int m_sentBytes; + internal int m_receivedBytes; + + internal long m_bytesAllocated; + + internal NetPeerStatistics(NetPeer peer) + { + m_peer = peer; + Reset(); + } + + internal void Reset() + { + m_sentPackets = 0; + m_receivedPackets = 0; + + m_sentMessages = 0; + m_receivedMessages = 0; + + m_sentBytes = 0; + m_receivedBytes = 0; + + m_bytesAllocated = 0; + } + + /// + /// Gets the number of sent packets since the NetPeer was initialized + /// + public int SentPackets { get { return m_sentPackets; } } + + /// + /// Gets the number of received packets since the NetPeer was initialized + /// + public int ReceivedPackets { get { return m_receivedPackets; } } + + /// + /// Gets the number of sent messages since the NetPeer was initialized + /// + public int SentMessages { get { return m_sentMessages; } } + + /// + /// Gets the number of received messages since the NetPeer was initialized + /// + public int ReceivedMessages { get { return m_receivedMessages; } } + + /// + /// Gets the number of sent bytes since the NetPeer was initialized + /// + public int SentBytes { get { return m_sentBytes; } } + + /// + /// Gets the number of received bytes since the NetPeer was initialized + /// + public int ReceivedBytes { get { return m_receivedBytes; } } + + /// + /// Gets the number of bytes allocated (and possibly garbage collected) for message storage + /// + public long BytesAllocated { get { return m_bytesAllocated; } } + + /// + /// Gets the number of bytes in the recycled pool + /// + public int BytesInRecyclePool { get { return m_peer.m_storedBytes; } } + + [Conditional("DEBUG")] + internal void PacketSent(int numBytes, int numMessages) + { + m_sentPackets++; + m_sentBytes += numBytes; + m_sentMessages += numMessages; + } + + [Conditional("DEBUG")] + internal void PacketReceived(int numBytes, int numMessages) + { + m_receivedPackets++; + m_receivedBytes += numBytes; + m_receivedMessages += numMessages; + } + + public override string ToString() + { + StringBuilder bdr = new StringBuilder(); + bdr.AppendLine(m_peer.ConnectionsCount.ToString() + " connections"); + bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets"); + bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages in " + m_receivedPackets + " packets"); + bdr.AppendLine("Allocated " + m_bytesAllocated + " bytes"); + bdr.AppendLine("Recycled pool " + m_peer.m_storedBytes + " bytes"); + return bdr.ToString(); + } + } +} diff --git a/Lidgren.Network/NetPeerStatus.cs b/Lidgren.Network/NetPeerStatus.cs new file mode 100644 index 0000000..bd711aa --- /dev/null +++ b/Lidgren.Network/NetPeerStatus.cs @@ -0,0 +1,30 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + public enum NetPeerStatus + { + NotRunning = 0, + Starting = 1, + Running = 2, + ShutdownRequested = 3, + } +} diff --git a/Lidgren.Network/NetQueue.cs b/Lidgren.Network/NetQueue.cs new file mode 100644 index 0000000..6344672 --- /dev/null +++ b/Lidgren.Network/NetQueue.cs @@ -0,0 +1,164 @@ +using System; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Thread safe queue with TryDequeue() + /// + [DebuggerDisplay("Count={m_size}")] + public sealed class NetQueue + { + // Example: + // m_capacity = 8 + // m_size = 6 + // m_head = 4 + // + // [0] item + // [1] item (tail = ((head + size - 1) % capacity) + // [2] + // [3] + // [4] item (head) + // [5] item + // [6] item + // [7] item + // + private T[] m_items; + private object m_lock; + private int m_size; + private int m_head; + + public int Count { get { return m_size; } } + + public NetQueue(int initialCapacity) + { + m_lock = new object(); + m_items = new T[initialCapacity]; + } + + /// + /// Places an item last/tail of the queue + /// + public void Enqueue(T item) + { +#if DEBUG + if (typeof(T) == typeof(NetOutgoingMessage)) + { + NetOutgoingMessage om = item as NetOutgoingMessage; + if (om != null) + if (om.m_type == NetMessageType.Error) + throw new NetException("Enqueuing error message!"); + } +#endif + lock (m_lock) + { + if (m_size == m_items.Length) + SetCapacity(m_items.Length + 8); + + int slot = (m_head + m_size) % m_items.Length; + m_items[slot] = item; + m_size++; + } + } + + /// + /// Places an item first, at the head of the queue + /// + public void EnqueueFirst(T item) + { + lock (m_lock) + { + if (m_size >= m_items.Length) + SetCapacity(m_items.Length + 8); + + m_head--; + if (m_head < 0) + m_head = m_items.Length - 1; + m_items[m_head] = item; + m_size++; + } + } + + private void SetCapacity(int newCapacity) + { + if (m_size == 0) + { + lock (m_lock) + { + if (m_size == 0) + { + m_items = new T[newCapacity]; + m_head = 0; + return; + } + } + } + + T[] newItems = new T[newCapacity]; + + lock (m_lock) + { + if (m_head + m_size - 1 < m_items.Length) + { + Array.Copy(m_items, m_head, newItems, 0, m_size); + } + else + { + Array.Copy(m_items, m_head, newItems, 0, m_items.Length - m_head); + Array.Copy(m_items, 0, newItems, m_items.Length - m_head, (m_size - (m_items.Length - m_head))); + } + + m_items = newItems; + m_head = 0; + } + } + + /// + /// Gets an item from the head of the queue, or returns default(T) if empty + /// + public T TryDequeue() + { + if (m_size == 0) + return default(T); + + lock (m_lock) + { + if (m_size == 0) + return default(T); + + T retval = m_items[m_head]; + m_items[m_head] = default(T); + + m_head = (m_head + 1) % m_items.Length; + m_size--; + + return retval; + } + } + + public bool Contains(T item) + { + lock (m_lock) + { + int ptr = m_head; + for (int i = 0; i < m_size; i++) + { + if (m_items[ptr].Equals(item)) + return true; + ptr = (ptr + 1) % m_items.Length; + } + } + return false; + } + + public void Clear() + { + lock (m_lock) + { + for (int i = 0; i < m_items.Length; i++) + m_items[i] = default(T); + m_head = 0; + } + } + } +} diff --git a/Lidgren.Network/NetRandom.cs b/Lidgren.Network/NetRandom.cs new file mode 100644 index 0000000..5523248 --- /dev/null +++ b/Lidgren.Network/NetRandom.cs @@ -0,0 +1,399 @@ +using System; +using System.Diagnostics; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// A fast random number generator for .NET + /// Colin Green, January 2005 + /// + /// September 4th 2005 + /// Added NextBytesUnsafe() - commented out by default. + /// Fixed bug in Reinitialise() - y,z and w variables were not being reset. + /// + /// Key points: + /// 1) Based on a simple and fast xor-shift pseudo random number generator (RNG) specified in: + /// Marsaglia, George. (2003). Xorshift RNGs. + /// http://www.jstatsoft.org/v08/i14/xorshift.pdf + /// + /// This particular implementation of xorshift has a period of 2^128-1. See the above paper to see + /// how this can be easily extened if you need a longer period. At the time of writing I could find no + /// information on the period of System.Random for comparison. + /// + /// 2) Faster than System.Random. Up to 15x faster, depending on which methods are called. + /// + /// 3) Direct replacement for System.Random. This class implements all of the methods that System.Random + /// does plus some additional methods. The like named methods are functionally equivalent. + /// + /// 4) Allows fast re-initialisation with a seed, unlike System.Random which accepts a seed at construction + /// time which then executes a relatively expensive initialisation routine. This provides a vast speed improvement + /// if you need to reset the pseudo-random number sequence many times, e.g. if you want to re-generate the same + /// sequence many times. An alternative might be to cache random numbers in an array, but that approach is limited + /// by memory capacity and the fact that you may also want a large number of different sequences cached. Each sequence + /// can each be represented by a single seed value (int) when using FastRandom. + /// + /// Notes. + /// A further performance improvement can be obtained by declaring local variables as static, thus avoiding + /// re-allocation of variables on each call. However care should be taken if multiple instances of + /// FastRandom are in use or if being used in a multi-threaded environment. + /// + /// + public sealed class NetRandom : Random + { + public static NetRandom Instance = new NetRandom(); + + protected override double Sample() + { + return NextDouble(); + } + + // The +1 ensures NextDouble doesn't generate 1.0 + private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0); + private const double c_realUnitUint = 1.0 / ((double)uint.MaxValue + 1.0); + private const uint c_y = 842502087, c_z = 3579807591, c_w = 273326509; + + private static int m_extraSeed = 42; + + uint m_x, m_y, m_z, m_w; + + /// + /// Returns a random seed based on time and working set + /// + public static int GetRandomSeed() + { + int seed = (int)Environment.TickCount; + + try + { + // tickcount + gettimestamp + workingset should be random enough + if (!string.IsNullOrEmpty(Environment.CommandLine)) + seed ^= Environment.CommandLine.GetHashCode(); + seed ^= (int)(Stopwatch.GetTimestamp()); + seed ^= (int)(Environment.WorkingSet); // will return 0 on mono + } + catch + { + // maybe commandline etc is not available, TickCount will have to do + } + + int extraSeed = Interlocked.Increment(ref m_extraSeed); + + return seed + extraSeed; + } + + /// + /// Initialises a new instance using time dependent seed. + /// + public NetRandom() + { + // Initialise using the system tick count + Reinitialise(GetRandomSeed()); + } + + /// + /// Initialises a new instance using an int value as seed. + /// This constructor signature is provided to maintain compatibility with + /// System.Random + /// + public NetRandom(int seed) + { + Reinitialise(seed); + } + + /// + /// Reinitialises using an int value as a seed. + /// + /// + public void Reinitialise(int seed) + { + // The only stipulation stated for the xorshift RNG is that at least one of + // the seeds x,y,z,w is non-zero. We fulfill that requirement by only allowing + // resetting of the x seed + m_x = (uint)seed; + m_y = c_y; + m_z = c_z; + m_w = c_w; + } + + /// + /// Generates a uint. Values returned are over the full range of a uint, + /// uint.MinValue to uint.MaxValue, including the min and max values. + /// + [CLSCompliant(false)] + public uint NextUInt() + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + return (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))); + } + + /// + /// Generates a random int. Values returned are over the range 0 to int.MaxValue-1. + /// MaxValue is not generated to remain functionally equivalent to System.Random.Next(). + /// If you require an int from the full range, including negative values then call + /// NextUint() and cast the value to an int. + /// + /// + public override int Next() + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + return (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)))); + } + + /// + /// Generates a random int over the range 0 to upperBound-1, and not including upperBound. + /// + public override int Next(int maxValue) + { + if (maxValue < 0) + throw new ArgumentOutOfRangeException("maxValue", maxValue, "maxValue must be >=0"); + + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + + // The explicit int cast before the first multiplication gives better performance. + // See comments in NextDouble. + return (int)((c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))) * maxValue); + } + + /// + /// Generates a random int over the range minValue to maxValue-1, and not including maxValue. + /// maxValue must be >= minValue. minValue may be negative. + /// + public override int Next(int minValue, int maxValue) + { + if (minValue > maxValue) + throw new ArgumentOutOfRangeException("maxValue", maxValue, "maxValue must be >=minValue"); + + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + + // The explicit int cast before the first multiplication gives better performance. + // See comments in NextDouble. + int range = maxValue - minValue; + if (range < 0) + { // If range is <0 then an overflow has occured and must resort to using long integer arithmetic instead (slower). + // We also must use all 32 bits of precision, instead of the normal 31, which again is slower. + return minValue + (int)((c_realUnitUint * (double)(m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)))) * (double)((long)maxValue - (long)minValue)); + } + + // 31 bits of precision will suffice if range<=int.MaxValue. This allows us to cast to an int anf gain + // a little more performance. + return minValue + (int)((c_realUnitInt * (double)(int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))) * (double)range); + } + + /// + /// Generates a random double. Values returned are from 0.0 up to but not including 1.0. + /// + /// + public override double NextDouble() + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + + // Here we can gain a 2x speed improvement by generating a value that can be cast to + // an int instead of the more easily available uint. If we then explicitly cast to an + // int the compiler will then cast the int to a double to perform the multiplication, + // this final cast is a lot faster than casting from a uint to a double. The extra cast + // to an int is very fast (the allocated bits remain the same) and so the overall effect + // of the extra cast is a significant performance improvement. + return (c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))); + } + + /// + /// Generates a random double. Values returned are from 0.0 up to but not including 1.0. + /// + /// + public float NextFloat() + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + + // Here we can gain a 2x speed improvement by generating a value that can be cast to + // an int instead of the more easily available uint. If we then explicitly cast to an + // int the compiler will then cast the int to a double to perform the multiplication, + // this final cast is a lot faster than casting from a uint to a double. The extra cast + // to an int is very fast (the allocated bits remain the same) and so the overall effect + // of the extra cast is a significant performance improvement. + return (float)(c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))); + } + + /// + /// Generates a random double. Values returned are from 0.0 up to but not including roof + /// + /// + public float NextFloat(float roof) + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + + // Here we can gain a 2x speed improvement by generating a value that can be cast to + // an int instead of the more easily available uint. If we then explicitly cast to an + // int the compiler will then cast the int to a double to perform the multiplication, + // this final cast is a lot faster than casting from a uint to a double. The extra cast + // to an int is very fast (the allocated bits remain the same) and so the overall effect + // of the extra cast is a significant performance improvement. + float f = (float)(c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))); + + return f * roof; + } + + /// + /// Generates a random double. Values returned are from min up to but not including min + variance + /// + /// + public float NextFloat(float min, float variance) + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + + // Here we can gain a 2x speed improvement by generating a value that can be cast to + // an int instead of the more easily available uint. If we then explicitly cast to an + // int the compiler will then cast the int to a double to perform the multiplication, + // this final cast is a lot faster than casting from a uint to a double. The extra cast + // to an int is very fast (the allocated bits remain the same) and so the overall effect + // of the extra cast is a significant performance improvement. + float f = (float)(c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))); + + return min + f * variance; + } + + /// + /// If passed 0.7f it will return true 7 times out of 10 + /// + /// + public bool Chance(float percentChance) + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + + // Here we can gain a 2x speed improvement by generating a value that can be cast to + // an int instead of the more easily available uint. If we then explicitly cast to an + // int the compiler will then cast the int to a double to perform the multiplication, + // this final cast is a lot faster than casting from a uint to a double. The extra cast + // to an int is very fast (the allocated bits remain the same) and so the overall effect + // of the extra cast is a significant performance improvement. + double hit = (c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))); + return (hit < percentChance); + } + + /// + /// Returns a System.Single larger or equal to 0 and smaller than 1.0f - gaussian distributed! + /// + public float NextGaussian() + { + return (float)((NextDouble() + NextDouble() + NextDouble()) / 3.0); + } + + /// + /// Fills the provided byte array with random bytes. + /// Increased performance is achieved by dividing and packaging bits directly from the + /// random number generator and storing them in 4 byte 'chunks'. + /// + /// + public override void NextBytes(byte[] buffer) + { + // Fill up the bulk of the buffer in chunks of 4 bytes at a time. + uint x = this.m_x, y = this.m_y, z = this.m_z, w = this.m_w; + int i = 0; + uint t; + for (; i < buffer.Length - 3; ) + { + // Generate 4 bytes. + t = (x ^ (x << 11)); + x = y; y = z; z = w; + w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); + + buffer[i++] = (byte)(w & 0x000000FF); + buffer[i++] = (byte)((w & 0x0000FF00) >> 8); + buffer[i++] = (byte)((w & 0x00FF0000) >> 16); + buffer[i++] = (byte)((w & 0xFF000000) >> 24); + } + + // Fill up any remaining bytes in the buffer. + if (i < buffer.Length) + { + // Generate 4 bytes. + t = (x ^ (x << 11)); + x = y; y = z; z = w; + w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); + + buffer[i++] = (byte)(w & 0x000000FF); + if (i < buffer.Length) + { + buffer[i++] = (byte)((w & 0x0000FF00) >> 8); + if (i < buffer.Length) + { + buffer[i++] = (byte)((w & 0x00FF0000) >> 16); + if (i < buffer.Length) + { + buffer[i] = (byte)((w & 0xFF000000) >> 24); + } + } + } + } + this.m_x = x; this.m_y = y; this.m_z = z; this.m_w = w; + } + + + // /// + // /// A version of NextBytes that uses a pointer to set 4 bytes of the byte buffer in one operation + // /// thus providing a nice speedup. Note that this requires the unsafe compilation flag to be specified + // /// and so is commented out by default. + // /// + // /// + // public unsafe void NextBytesUnsafe(byte[] buffer) + // { + // if(buffer.Length % 4 != 0) + // throw new ArgumentException("Buffer length must be divisible by 4", "buffer"); + // + // uint x=this.x, y=this.y, z=this.z, w=this.w; + // uint t; + // + // fixed(byte* pByte0 = buffer) + // { + // uint* pDWord = (uint*)pByte0; + // for(int i = 0, len = buffer.Length>>2; i < len; i++) + // { + // t=(x^(x<<11)); + // x=y; y=z; z=w; + // *pDWord++ = w = (w^(w>>19))^(t^(t>>8)); + // } + // } + // + // this.x=x; this.y=y; this.z=z; this.w=w; + // } + + // Buffer 32 bits in bitBuffer, return 1 at a time, keep track of how many have been returned + // with bitBufferIdx. + uint bitBuffer; + int bitBufferIdx = 32; + + /// + /// Generates random bool. + /// Increased performance is achieved by buffering 32 random bits for + /// future calls. Thus the random number generator is only invoked once + /// in every 32 calls. + /// + /// + public bool NextBool() + { + if (bitBufferIdx == 32) + { + // Generate 32 more bits. + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + bitBuffer = m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)); + + // Reset the idx that tells us which bit to read next. + bitBufferIdx = 1; + return (bitBuffer & 0x1) == 1; + } + + bitBufferIdx++; + return ((bitBuffer >>= 1) & 0x1) == 1; + } + } +} diff --git a/Lidgren.Network/NetServer.cs b/Lidgren.Network/NetServer.cs new file mode 100644 index 0000000..d164909 --- /dev/null +++ b/Lidgren.Network/NetServer.cs @@ -0,0 +1,33 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; + +namespace Lidgren.Network +{ + public class NetServer : NetPeer + { + public NetServer(NetPeerConfiguration config) + : base(config) + { + // force this to true + config.AcceptIncomingConnections = true; + } + } +} diff --git a/Lidgren.Network/NetTime.cs b/Lidgren.Network/NetTime.cs new file mode 100644 index 0000000..bd217ac --- /dev/null +++ b/Lidgren.Network/NetTime.cs @@ -0,0 +1,60 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +#define IS_STOPWATCH_AVAILABLE + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Time service + /// + public static class NetTime + { +#if IS_STOPWATCH_AVAILABLE + private static long s_timeInitialized = Stopwatch.GetTimestamp(); + private static double s_dInvFreq = 1.0 / (double)Stopwatch.Frequency; + + /// + /// Get number of seconds since the application started + /// + public static double Now { get { return (double)(Stopwatch.GetTimestamp() - s_timeInitialized) * s_dInvFreq; } } +#else + /// + /// Get number of seconds since the application started + /// + public static double Now { get { return (double)Environment.TickCount / 1000.0; } } +#endif + public static double GetRemoteNow(NetConnection conn) + { + return Now - conn.m_remoteToLocalNetTime; + } + + public static string ToReadable(double seconds) + { + if (seconds > 60) + return TimeSpan.FromSeconds(seconds).ToString(); + return (seconds * 1000.0).ToString("N2") + " ms"; + } + } +} \ No newline at end of file diff --git a/Lidgren.Network/NetUtility.cs b/Lidgren.Network/NetUtility.cs new file mode 100644 index 0000000..06843d5 --- /dev/null +++ b/Lidgren.Network/NetUtility.cs @@ -0,0 +1,262 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; + +namespace Lidgren.Network +{ + /// + /// Utility methods + /// + public static class NetUtility + { + private static Regex s_regIP; + + /// + /// Get IP address from notation (xxx.xxx.xxx.xxx) or hostname + /// + public static IPAddress Resolve(string ipOrHost) + { + if (string.IsNullOrEmpty(ipOrHost)) + throw new ArgumentException("Supplied string must not be empty", "ipOrHost"); + + ipOrHost = ipOrHost.Trim(); + + if (s_regIP == null) + { + string expression = "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b"; + RegexOptions options = RegexOptions.Compiled; + s_regIP = new Regex(expression, options); + } + + // is it an ip number string? + IPAddress ipAddress = null; + if (s_regIP.Match(ipOrHost).Success && IPAddress.TryParse(ipOrHost, out ipAddress)) + return ipAddress; + + // ok must be a host name + IPHostEntry entry; + try + { + entry = Dns.GetHostEntry(ipOrHost); + if (entry == null) + return null; + + // check each entry for a valid IP address + foreach (IPAddress ipCurrent in entry.AddressList) + { + string sIP = ipCurrent.ToString(); + bool isIP = s_regIP.Match(sIP).Success && IPAddress.TryParse(sIP, out ipAddress); + if (isIP) + break; + } + if (ipAddress == null) + return null; + + return ipAddress; + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostNotFound) + { + //LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost)); + return null; + } + else + { + throw; + } + } + } + + private static NetworkInterface GetNetworkInterface() + { + IPGlobalProperties computerProperties = IPGlobalProperties.GetIPGlobalProperties(); + if (computerProperties == null) + return null; + + NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); + if (nics == null || nics.Length < 1) + return null; + + NetworkInterface best = null; + foreach (NetworkInterface adapter in nics) + { + if (adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback || adapter.NetworkInterfaceType == NetworkInterfaceType.Unknown) + continue; + if (!adapter.Supports(NetworkInterfaceComponent.IPv4)) + continue; + if (best == null) + best = adapter; + if (adapter.OperationalStatus != OperationalStatus.Up) + continue; + + // A computer could have several adapters (more than one network card) + // here but just return the first one for now... + return adapter; + } + return best; + } + + public static PhysicalAddress GetMacAddress() + { + NetworkInterface ni = GetNetworkInterface(); + if (ni == null) + return null; + return ni.GetPhysicalAddress(); + } + + public static string ToHexString(long data) + { + return ToHexString(BitConverter.GetBytes(data)); + } + + public static string ToHexString(byte[] data) + { + StringBuilder sb = new StringBuilder(data.Length * 2); + foreach (byte b in data) + { + sb.AppendFormat("{0:X2}", b); + } + return sb.ToString(); + } + + /// + /// Gets my local IP address (not necessarily external) and subnet mask + /// + public static IPAddress GetMyAddress(out IPAddress mask) + { + NetworkInterface ni = GetNetworkInterface(); + if (ni == null) + { + mask = null; + return null; + } + + IPInterfaceProperties properties = ni.GetIPProperties(); + foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses) + { + if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) + { + mask = unicastAddress.IPv4Mask; + return unicastAddress.Address; + } + } + + mask = null; + return null; + } + + /// + /// Returns true if the IPEndPoint supplied is on the same subnet as this host + /// + public static bool IsLocal(IPEndPoint endpoint) + { + if (endpoint == null) + return false; + return IsLocal(endpoint.Address); + } + + /// + /// Returns true if the IPAddress supplied is on the same subnet as this host + /// + public static bool IsLocal(IPAddress remote) + { + IPAddress mask; + IPAddress local = GetMyAddress(out mask); + + if (mask == null) + return false; + + uint maskBits = BitConverter.ToUInt32(mask.GetAddressBytes(), 0); + uint remoteBits = BitConverter.ToUInt32(remote.GetAddressBytes(), 0); + uint localBits = BitConverter.ToUInt32(local.GetAddressBytes(), 0); + + // compare network portions + return ((remoteBits & maskBits) == (localBits & maskBits)); + } + + /// + /// Returns how many bits are necessary to hold a certain number + /// + [CLSCompliant(false)] + public static int BitsToHoldUInt(uint value) + { + int bits = 1; + while ((value >>= 1) != 0) + bits++; + return bits; + } + + /// + /// Returns how many bytes are required to hold a certain number of bits + /// + public static int BytesToHoldBits(int numBits) + { + return (numBits + 7) / 8; + } + + [CLSCompliant(false)] + public static UInt32 SwapByteOrder(UInt32 value) + { + return + ((value & 0xff000000) >> 24) | + ((value & 0x00ff0000) >> 8) | + ((value & 0x0000ff00) << 8) | + ((value & 0x000000ff) << 24); + } + + [CLSCompliant(false)] + public static UInt64 SwapByteOrder(UInt64 value) + { + return + ((value & 0xff00000000000000L) >> 56) | + ((value & 0x00ff000000000000L) >> 40) | + ((value & 0x0000ff0000000000L) >> 24) | + ((value & 0x000000ff00000000L) >> 8) | + ((value & 0x00000000ff000000L) << 8) | + ((value & 0x0000000000ff0000L) << 24) | + ((value & 0x000000000000ff00L) << 40) | + ((value & 0x00000000000000ffL) << 56); + } + + public static bool CompareElements(byte[] one, byte[] two) + { + if (one.Length != two.Length) + return false; + for (int i = 0; i < one.Length; i++) + if (one[i] != two[i]) + return false; + return true; + } + + public static byte[] ToByteArray(String hexString) + { + byte[] retval = new byte[hexString.Length / 2]; + for (int i = 0; i < hexString.Length; i += 2) + retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); + return retval; + } + } +} diff --git a/Lidgren.Network/Properties/AssemblyInfo.cs b/Lidgren.Network/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6681d45 --- /dev/null +++ b/Lidgren.Network/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Lidgren.Network")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Lidgren.Network")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4914374-a2fb-4e56-bf94-60d4b81c10a1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: System.CLSCompliant(true)] \ No newline at end of file diff --git a/Samples/ChatClient/ChatClient.csproj b/Samples/ChatClient/ChatClient.csproj new file mode 100644 index 0000000..ede65f6 --- /dev/null +++ b/Samples/ChatClient/ChatClient.csproj @@ -0,0 +1,94 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {321F68AE-7F97-415E-A3F9-7C477EFF95EE} + WinExe + Properties + ChatClient + ChatClient + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + {773069DA-B66E-4667-ADCB-0D215AD8CF3E} + SamplesCommon + + + + + \ No newline at end of file diff --git a/Samples/ChatClient/Form1.Designer.cs b/Samples/ChatClient/Form1.Designer.cs new file mode 100644 index 0000000..b83d283 --- /dev/null +++ b/Samples/ChatClient/Form1.Designer.cs @@ -0,0 +1,103 @@ +namespace ChatClient +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // richTextBox1 + // + this.richTextBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.richTextBox1.Location = new System.Drawing.Point(12, 12); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(500, 220); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox1.Location = new System.Drawing.Point(12, 238); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(321, 22); + this.textBox1.TabIndex = 1; + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button1.Location = new System.Drawing.Point(339, 236); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(79, 23); + this.button1.TabIndex = 2; + this.button1.Text = "Send"; + this.button1.UseVisualStyleBackColor = true; + // + // button2 + // + this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button2.Location = new System.Drawing.Point(424, 236); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(88, 23); + this.button2.TabIndex = 3; + this.button2.Text = "Settings"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(524, 272); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.richTextBox1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "Form1"; + this.Text = "Chat client"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + public System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + } +} + diff --git a/Samples/ChatClient/Form1.cs b/Samples/ChatClient/Form1.cs new file mode 100644 index 0000000..a835a03 --- /dev/null +++ b/Samples/ChatClient/Form1.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using SamplesCommon; + +namespace ChatClient +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + + textBox1.KeyDown += new KeyEventHandler(textBox1_KeyDown); + } + + void textBox1_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Return) + { + string txt = textBox1.Text.Trim(); + Program.Input(txt); + textBox1.Text = ""; + } + } + + private void button2_Click(object sender, EventArgs e) + { + if (Program.SettingsWindow == null || Program.SettingsWindow.IsDisposed) + Program.SettingsWindow = new NetPeerSettingsWindow("Client settings", Program.Client); + if (Program.SettingsWindow.Visible) + Program.SettingsWindow.Hide(); + else + Program.SettingsWindow.Show(); + } + } +} diff --git a/Samples/ChatClient/Form1.resx b/Samples/ChatClient/Form1.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/Samples/ChatClient/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ChatClient/Program.cs b/Samples/ChatClient/Program.cs new file mode 100644 index 0000000..d9cd174 --- /dev/null +++ b/Samples/ChatClient/Program.cs @@ -0,0 +1,105 @@ +using System; +using System.Threading; +using System.Windows.Forms; + +using Lidgren.Network; + +using SamplesCommon; + +namespace ChatClient +{ + public class ChatMessage + { + public string Sender; + public string Text; + } + + static class Program + { + public static Form1 MainForm; + public static NetClient Client; + public static NetPeerSettingsWindow SettingsWindow; + + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + MainForm = new Form1(); + + Client = new NetClient(new NetPeerConfiguration("Chat")); + Client.Start(); + + Display("Type 'connect ' to connect to a server"); + + Application.Idle += new EventHandler(AppLoop); + Application.Run(MainForm); + } + + private static void Display(string text) + { + NativeMethods.AppendText(MainForm.richTextBox1, text); + } + + public static void Input(string input) + { + if (input.ToLowerInvariant().StartsWith("connect ")) + { + string host = input.Substring(8).Trim(); + if (string.IsNullOrEmpty(host)) + host = "localhost"; + + Client.Connect(host, 14242); + return; + } + + // send chat message + ChatMessage cm = new ChatMessage(); + cm.Sender = Client.UniqueIdentifier.ToString(); + cm.Text = input; + + NetOutgoingMessage om = Client.CreateMessage(); + om.WriteAllFields(cm); + Client.SendMessage(om, NetDeliveryMethod.ReliableOrdered); + } + + static void AppLoop(object sender, EventArgs e) + { + while (NativeMethods.AppStillIdle) + { + NetIncomingMessage msg = Client.ReadMessage(); + if (msg != null) + { + switch (msg.MessageType) + { + case NetIncomingMessageType.VerboseDebugMessage: + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.ErrorMessage: + case NetIncomingMessageType.WarningMessage: + // print any diagnostics message + Display(msg.ReadString()); + break; + + case NetIncomingMessageType.StatusChanged: + // print changes in connection(s) status + NetConnectionStatus status = (NetConnectionStatus)msg.ReadByte(); + string reason = msg.ReadString(); + Display("Status: " + status + " (" + reason + ")"); + + break; + + case NetIncomingMessageType.Data: + + ChatMessage cm = new ChatMessage(); + msg.ReadAllFields(cm); + + Display("Received from " + cm.Sender + ": " + cm.Text); + break; + } + Client.Recycle(msg); + } + Thread.Sleep(1); + } + } + } +} diff --git a/Samples/ChatClient/Properties/AssemblyInfo.cs b/Samples/ChatClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0524de3 --- /dev/null +++ b/Samples/ChatClient/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ChatClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("ChatClient")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2438660c-dd5f-45ac-bf17-69ed0b1c7dfa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/ChatClient/Properties/Resources.Designer.cs b/Samples/ChatClient/Properties/Resources.Designer.cs new file mode 100644 index 0000000..e24e51d --- /dev/null +++ b/Samples/ChatClient/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ChatClient.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ChatClient.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Samples/ChatClient/Properties/Resources.resx b/Samples/ChatClient/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Samples/ChatClient/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ChatClient/Properties/Settings.Designer.cs b/Samples/ChatClient/Properties/Settings.Designer.cs new file mode 100644 index 0000000..59a1aa0 --- /dev/null +++ b/Samples/ChatClient/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ChatClient.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Samples/ChatClient/Properties/Settings.settings b/Samples/ChatClient/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/Samples/ChatClient/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Samples/ChatServer/ChatServer.csproj b/Samples/ChatServer/ChatServer.csproj new file mode 100644 index 0000000..bca2ec8 --- /dev/null +++ b/Samples/ChatServer/ChatServer.csproj @@ -0,0 +1,94 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {E2711561-B3C9-4580-B054-891CE54E15EE} + WinExe + Properties + ChatServer + ChatServer + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + {773069DA-B66E-4667-ADCB-0D215AD8CF3E} + SamplesCommon + + + + + \ No newline at end of file diff --git a/Samples/ChatServer/Form1.Designer.cs b/Samples/ChatServer/Form1.Designer.cs new file mode 100644 index 0000000..58d0b82 --- /dev/null +++ b/Samples/ChatServer/Form1.Designer.cs @@ -0,0 +1,77 @@ +namespace ChatServer +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // richTextBox1 + // + this.richTextBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.richTextBox1.Location = new System.Drawing.Point(12, 12); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(500, 221); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.button1.Location = new System.Drawing.Point(12, 239); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(103, 23); + this.button1.TabIndex = 1; + this.button1.Text = "Settings"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(524, 272); + this.Controls.Add(this.button1); + this.Controls.Add(this.richTextBox1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "Form1"; + this.Text = "Chat server"; + this.ResumeLayout(false); + + } + + #endregion + + public System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.Button button1; + } +} + diff --git a/Samples/ChatServer/Form1.cs b/Samples/ChatServer/Form1.cs new file mode 100644 index 0000000..b27f291 --- /dev/null +++ b/Samples/ChatServer/Form1.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using SamplesCommon; + +namespace ChatServer +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + if (Program.SettingsWindow == null || Program.SettingsWindow.IsDisposed) + Program.SettingsWindow = new NetPeerSettingsWindow("Client settings", Program.Server); + if (Program.SettingsWindow.Visible) + Program.SettingsWindow.Hide(); + else + Program.SettingsWindow.Show(); + } + } +} diff --git a/Samples/ChatServer/Form1.resx b/Samples/ChatServer/Form1.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/Samples/ChatServer/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ChatServer/Program.cs b/Samples/ChatServer/Program.cs new file mode 100644 index 0000000..588ef46 --- /dev/null +++ b/Samples/ChatServer/Program.cs @@ -0,0 +1,92 @@ +using System; +using System.Threading; +using System.Windows.Forms; + +using Lidgren.Network; + +using SamplesCommon; + +namespace ChatServer +{ + public class ChatMessage + { + public string Sender { get; set; } + public string Text { get; set; } + } + + static class Program + { + public static Form1 MainForm; + public static NetServer Server; + public static NetPeerSettingsWindow SettingsWindow; + + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + MainForm = new Form1(); + + // create a configuration + NetPeerConfiguration config = new NetPeerConfiguration("Chat"); + config.Port = 14242; + + // create and start server + Server = new NetServer(config); + Server.Start(); + + Application.Idle += new EventHandler(AppLoop); + Application.Run(MainForm); + } + + private static void Display(string text) + { + NativeMethods.AppendText(MainForm.richTextBox1, text); + } + + static void AppLoop(object sender, EventArgs e) + { + while (NativeMethods.AppStillIdle) + { + NetIncomingMessage msg = Server.WaitMessage(100); + if (msg != null) + { + switch (msg.MessageType) + { + case NetIncomingMessageType.VerboseDebugMessage: + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.ErrorMessage: + case NetIncomingMessageType.WarningMessage: + // print any library message + Display(msg.ReadString()); + break; + + case NetIncomingMessageType.StatusChanged: + // print changes in connection(s) status + NetConnectionStatus status = (NetConnectionStatus)msg.ReadByte(); + string reason = msg.ReadString(); + Display(msg.SenderConnection + " status: " + status + " (" + reason + ")"); + + break; + + case NetIncomingMessageType.Data: + + // read chat message + ChatMessage cm = new ChatMessage(); + msg.ReadAllProperties(cm); + + // Forward all data to all clients (including sender for debugging purposes) + NetOutgoingMessage om = Server.CreateMessage(); + om.WriteAllProperties(cm, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + + Display("Forwarding text from " + cm.Sender + " to all clients: " + cm.Text); + Server.SendMessage(om, Server.Connections, NetDeliveryMethod.ReliableUnordered, 0); + + break; + } + Server.Recycle(msg); + } + } + } + } +} diff --git a/Samples/ChatServer/Properties/AssemblyInfo.cs b/Samples/ChatServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4fe7863 --- /dev/null +++ b/Samples/ChatServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ChatServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("ChatServer")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2438660c-dd5f-45ac-bf17-69ed0b1c7dfa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/ChatServer/Properties/Resources.Designer.cs b/Samples/ChatServer/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6b80a71 --- /dev/null +++ b/Samples/ChatServer/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ChatServer.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ChatServer.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Samples/ChatServer/Properties/Resources.resx b/Samples/ChatServer/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Samples/ChatServer/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ChatServer/Properties/Settings.Designer.cs b/Samples/ChatServer/Properties/Settings.Designer.cs new file mode 100644 index 0000000..b8aa7ce --- /dev/null +++ b/Samples/ChatServer/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ChatServer.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Samples/ChatServer/Properties/Settings.settings b/Samples/ChatServer/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/Samples/ChatServer/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Samples/DurableClient/DurableClient.csproj b/Samples/DurableClient/DurableClient.csproj new file mode 100644 index 0000000..f89b8b2 --- /dev/null +++ b/Samples/DurableClient/DurableClient.csproj @@ -0,0 +1,94 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {0B4B02BB-0F43-4466-A369-0682281AF60E} + WinExe + Properties + DurableClient + DurableClient + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + {773069DA-B66E-4667-ADCB-0D215AD8CF3E} + SamplesCommon + + + + + \ No newline at end of file diff --git a/Samples/DurableClient/Form1.Designer.cs b/Samples/DurableClient/Form1.Designer.cs new file mode 100644 index 0000000..017687c --- /dev/null +++ b/Samples/DurableClient/Form1.Designer.cs @@ -0,0 +1,112 @@ +namespace DurableClient +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.button2 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // richTextBox1 + // + this.richTextBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.richTextBox1.Location = new System.Drawing.Point(12, 218); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(493, 124); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + // + // button1 + // + this.button1.Location = new System.Drawing.Point(232, 10); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 1; + this.button1.Text = "Connect"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(12, 12); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(214, 22); + this.textBox1.TabIndex = 2; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 40); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(38, 13); + this.label1.TabIndex = 3; + this.label1.Text = "label1"; + // + // button2 + // + this.button2.Location = new System.Drawing.Point(313, 10); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(75, 23); + this.button2.TabIndex = 4; + this.button2.Text = "Settings"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(517, 354); + this.Controls.Add(this.button2); + this.Controls.Add(this.label1); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.button1); + this.Controls.Add(this.richTextBox1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "Form1"; + this.Text = "Form1"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + public System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.TextBox textBox1; + public System.Windows.Forms.Label label1; + private System.Windows.Forms.Button button2; + } +} + diff --git a/Samples/DurableClient/Form1.cs b/Samples/DurableClient/Form1.cs new file mode 100644 index 0000000..4546ac8 --- /dev/null +++ b/Samples/DurableClient/Form1.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using SamplesCommon; + +namespace DurableClient +{ + public partial class Form1 : Form + { + private NetPeerSettingsWindow m_settingsWindow; + + public Form1() + { + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + if (string.IsNullOrEmpty(textBox1.Text)) + textBox1.Text = "localhost"; + + Program.Connect(textBox1.Text); + } + + private void button2_Click(object sender, EventArgs e) + { + if (m_settingsWindow == null) + { + m_settingsWindow = new NetPeerSettingsWindow("Durable client settings", Program.Client); + m_settingsWindow.Show(); + } + else + { + m_settingsWindow.Close(); + m_settingsWindow = null; + } + } + } +} diff --git a/Samples/DurableClient/Form1.resx b/Samples/DurableClient/Form1.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/Samples/DurableClient/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/DurableClient/Program.cs b/Samples/DurableClient/Program.cs new file mode 100644 index 0000000..d931de6 --- /dev/null +++ b/Samples/DurableClient/Program.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +using System.Threading; +using Lidgren.Network; +using SamplesCommon; +using System.Text; + +namespace DurableClient +{ + static class Program + { + public static Form1 MainForm; + public static NetClient Client; + + private static bool m_sendStuff; + + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + MainForm = new Form1(); + + NetPeerConfiguration config = new NetPeerConfiguration("durable"); + Client = new NetClient(config); + Client.Start(); + + Application.Idle += new EventHandler(AppLoop); + Application.Run(MainForm); + + Client.Shutdown("App exiting"); + } + + public static void Display(string text) + { + NativeMethods.AppendText(MainForm.richTextBox1, text); + } + + private static double m_nextSendReliableOrdered; + private static uint[] m_reliableOrderedNr = new uint[3]; + + private static double m_nextSendSequenced; + private static uint[] m_sequencedNr = new uint[3]; + + private static double m_lastLabelUpdate; + private const double kLabelUpdateFrequency = 0.25; + + static void AppLoop(object sender, EventArgs e) + { + while (NativeMethods.AppStillIdle) + { + NetIncomingMessage msg; + while ((msg = Client.WaitMessage(1)) != null) + { + switch (msg.MessageType) + { + case NetIncomingMessageType.VerboseDebugMessage: + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.WarningMessage: + case NetIncomingMessageType.ErrorMessage: + Display(msg.ReadString()); + break; + case NetIncomingMessageType.Data: + Display("Received data?!"); + break; + case NetIncomingMessageType.StatusChanged: + NetConnectionStatus status = (NetConnectionStatus)msg.ReadByte(); + string reason = msg.ReadString(); + Display("New status: " + status + " (" + reason + ")"); + + if (status == NetConnectionStatus.Connected) + { + // go + m_sendStuff = true; + } + break; + } + Client.Recycle(msg); + } + + if (m_sendStuff) + { + double now = NetTime.Now; + + float speed = 50.0f; + + float speedMultiplier = 1.0f / speed; + + int r = NetRandom.Instance.Next(3); + if (now > m_nextSendReliableOrdered) + { + NetOutgoingMessage om = Client.CreateMessage(5); + + uint rv = m_reliableOrderedNr[r]; + m_reliableOrderedNr[r]++; + + om.Write(rv); + + Client.SendMessage(om, NetDeliveryMethod.ReliableOrdered, r); + m_nextSendReliableOrdered = now + (NetRandom.Instance.NextFloat() * (0.01f * speedMultiplier)) + (0.005f * speedMultiplier); + } + + if (now > m_nextSendSequenced) + { + NetOutgoingMessage om = Client.CreateMessage(); + + uint v = m_sequencedNr[r]; + m_sequencedNr[r]++; + om.Write(v); + Client.SendMessage(om, NetDeliveryMethod.UnreliableSequenced, r); + m_nextSendSequenced = now + (NetRandom.Instance.NextFloat() * (0.01f * speedMultiplier)) + (0.005f * speedMultiplier); + } + + if (now > m_lastLabelUpdate + kLabelUpdateFrequency) + { + UpdateLabel(); + m_lastLabelUpdate = now; + } + } + } + } + + private static void UpdateLabel() + { + NetConnection conn = Client.ServerConnection; + if (conn != null) + { + StringBuilder bdr = new StringBuilder(); + bdr.Append(Client.Statistics.ToString()); + bdr.Append(conn.Statistics.ToString()); + + bdr.AppendLine("SENT Reliable ordered: " + m_reliableOrderedNr[0] + ", " + m_reliableOrderedNr[1] + ", " + m_reliableOrderedNr[2]); + bdr.AppendLine("SENT Sequenced: " + m_sequencedNr[0] + ", " + m_sequencedNr[1] + ", " + m_sequencedNr[2]); + MainForm.label1.Text = bdr.ToString(); + } + } + + public static void Connect(string host) + { + NetOutgoingMessage approval = Client.CreateMessage(); + approval.Write("durableschmurable"); + + Client.Connect(host, 14242, approval); + } + } +} diff --git a/Samples/DurableClient/Properties/AssemblyInfo.cs b/Samples/DurableClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..335e685 --- /dev/null +++ b/Samples/DurableClient/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DurableClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("DurableClient")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2438660c-dd5f-45ac-bf17-69ed0b1c7dfa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/DurableClient/Properties/Resources.Designer.cs b/Samples/DurableClient/Properties/Resources.Designer.cs new file mode 100644 index 0000000..37f440d --- /dev/null +++ b/Samples/DurableClient/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DurableClient.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DurableClient.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Samples/DurableClient/Properties/Resources.resx b/Samples/DurableClient/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Samples/DurableClient/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/DurableClient/Properties/Settings.Designer.cs b/Samples/DurableClient/Properties/Settings.Designer.cs new file mode 100644 index 0000000..77adcdd --- /dev/null +++ b/Samples/DurableClient/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DurableClient.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Samples/DurableClient/Properties/Settings.settings b/Samples/DurableClient/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/Samples/DurableClient/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Samples/DurableServer/DurableServer.csproj b/Samples/DurableServer/DurableServer.csproj new file mode 100644 index 0000000..8ce4de9 --- /dev/null +++ b/Samples/DurableServer/DurableServer.csproj @@ -0,0 +1,94 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {034984CA-FB37-44AF-BBF9-EC58ED75F5F3} + WinExe + Properties + DurableServer + DurableServer + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + {773069DA-B66E-4667-ADCB-0D215AD8CF3E} + SamplesCommon + + + + + \ No newline at end of file diff --git a/Samples/DurableServer/Form1.Designer.cs b/Samples/DurableServer/Form1.Designer.cs new file mode 100644 index 0000000..0df7cc4 --- /dev/null +++ b/Samples/DurableServer/Form1.Designer.cs @@ -0,0 +1,89 @@ +namespace DurableServer +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.button1 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // richTextBox1 + // + this.richTextBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.richTextBox1.Location = new System.Drawing.Point(12, 229); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(543, 167); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(38, 13); + this.label1.TabIndex = 1; + this.label1.Text = "label1"; + // + // button1 + // + this.button1.Location = new System.Drawing.Point(480, 200); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 2; + this.button1.Text = "Settings"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(567, 408); + this.Controls.Add(this.button1); + this.Controls.Add(this.label1); + this.Controls.Add(this.richTextBox1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "Form1"; + this.Text = "Durable server"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + public System.Windows.Forms.RichTextBox richTextBox1; + public System.Windows.Forms.Label label1; + private System.Windows.Forms.Button button1; + } +} + diff --git a/Samples/DurableServer/Form1.cs b/Samples/DurableServer/Form1.cs new file mode 100644 index 0000000..0a7f193 --- /dev/null +++ b/Samples/DurableServer/Form1.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using SamplesCommon; + +namespace DurableServer +{ + public partial class Form1 : Form + { + private NetPeerSettingsWindow m_settingsWindow; + + public Form1() + { + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + if (m_settingsWindow == null) + { + m_settingsWindow = new NetPeerSettingsWindow("Durable server settings", Program.Server); + m_settingsWindow.Show(); + } + else + { + m_settingsWindow.Close(); + m_settingsWindow = null; + } + } + } +} diff --git a/Samples/DurableServer/Form1.resx b/Samples/DurableServer/Form1.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/Samples/DurableServer/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/DurableServer/Program.cs b/Samples/DurableServer/Program.cs new file mode 100644 index 0000000..c07edc7 --- /dev/null +++ b/Samples/DurableServer/Program.cs @@ -0,0 +1,163 @@ +using System; +using System.Threading; +using System.Windows.Forms; + +using Lidgren.Network; + +using SamplesCommon; +using System.Text; + +namespace DurableServer +{ + static class Program + { + public static Form1 MainForm; + public static NetServer Server; + + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + MainForm = new Form1(); + + NetPeerConfiguration config = new NetPeerConfiguration("durable"); + config.Port = 14242; + config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); + Server = new NetServer(config); + Server.Start(); + + m_expectedReliableOrdered = new uint[3]; + m_reliableOrderedCorrect = new int[3]; + m_reliableOrderedErrors = new int[3]; + + m_expectedSequenced = new uint[3]; + m_sequencedCorrect = new int[3]; + m_sequencedErrors = new int[3]; + + Application.Idle += new EventHandler(AppLoop); + Application.Run(MainForm); + + Server.Shutdown("App exiting"); + } + + private static void Display(string text) + { + NativeMethods.AppendText(MainForm.richTextBox1, text); + } + + private static double m_lastLabelUpdate; + private const double kLabelUpdateFrequency = 0.25; + + private static uint[] m_expectedReliableOrdered; + private static int[] m_reliableOrderedCorrect; + private static int[] m_reliableOrderedErrors; + + private static uint[] m_expectedSequenced; + private static int[] m_sequencedCorrect; + private static int[] m_sequencedErrors; + + static void AppLoop(object sender, EventArgs e) + { + while (NativeMethods.AppStillIdle) + { + NetIncomingMessage msg; + while ((msg = Server.ReadMessage()) != null) + { + switch (msg.MessageType) + { + case NetIncomingMessageType.VerboseDebugMessage: + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.WarningMessage: + case NetIncomingMessageType.ErrorMessage: + Display(msg.ReadString()); + break; + case NetIncomingMessageType.ConnectionApproval: + string ok = msg.ReadString(); + if (ok == "durableschmurable") + msg.SenderConnection.Approve(); + else + msg.SenderConnection.Deny("You didn't say the secret word!"); + break; + case NetIncomingMessageType.StatusChanged: + NetConnectionStatus status = (NetConnectionStatus)msg.ReadByte(); + string reason = msg.ReadString(); + Display("New status: " + status + " (" + reason + ")"); + break; + case NetIncomingMessageType.Data: + uint nr = msg.ReadUInt32(); + int chan = msg.SequenceChannel; + switch (msg.DeliveryMethod) + { + case NetDeliveryMethod.ReliableOrdered: + if (nr != m_expectedReliableOrdered[chan]) + { + m_reliableOrderedErrors[chan]++; + m_expectedReliableOrdered[chan] = nr + 1; + } + else + { + m_reliableOrderedCorrect[chan]++; + m_expectedReliableOrdered[chan]++; + } + break; + case NetDeliveryMethod.UnreliableSequenced: + if (nr < m_expectedSequenced[chan]) + m_sequencedErrors[chan]++; + else + m_sequencedCorrect[chan]++; + m_expectedSequenced[chan] = nr + 1; + break; + } + break; + } + Server.Recycle(msg); + } + Thread.Sleep(0); + + double now = NetTime.Now; + if (now > m_lastLabelUpdate + kLabelUpdateFrequency) + { + UpdateLabel(); + m_lastLabelUpdate = now; + } + } + } + + private static void UpdateLabel() + { + if (Server.ConnectionsCount < 1) + { + // don't update! Keep old... + const string oldData = "(Note: OLD DATA - NO CONNECTIONS NOW)"; + if (!MainForm.label1.Text.EndsWith(oldData)) + MainForm.label1.Text += oldData; + } + else + { + StringBuilder bdr = new StringBuilder(); + bdr.Append(Server.Statistics.ToString()); + bdr.Append(Server.Connections[0].Statistics.ToString()); + bdr.AppendLine("RECEIVED Reliable ordered: " + + m_reliableOrderedCorrect[0] + ", " + + m_reliableOrderedCorrect[1] + ", " + + m_reliableOrderedCorrect[2] + + " received; " + + m_reliableOrderedErrors[0] + ", " + + m_reliableOrderedErrors[1] + ", " + + m_reliableOrderedErrors[2] + + " errors"); + bdr.AppendLine("RECEIVED Sequenced: " + + m_sequencedCorrect[0] + ", " + + m_sequencedCorrect[1] + ", " + + m_sequencedCorrect[2] + + " received; " + + m_sequencedErrors[0] + ", " + + m_sequencedErrors[1] + ", " + + m_sequencedErrors[2] + + " errors"); + MainForm.label1.Text = bdr.ToString(); + } + } + } +} diff --git a/Samples/DurableServer/Properties/AssemblyInfo.cs b/Samples/DurableServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e825b49 --- /dev/null +++ b/Samples/DurableServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DurableServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("DurableServer")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2438660c-dd5f-45ac-bf17-69ed0b1c7dfa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/DurableServer/Properties/Resources.Designer.cs b/Samples/DurableServer/Properties/Resources.Designer.cs new file mode 100644 index 0000000..b898ce5 --- /dev/null +++ b/Samples/DurableServer/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DurableServer.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DurableServer.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Samples/DurableServer/Properties/Resources.resx b/Samples/DurableServer/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Samples/DurableServer/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/DurableServer/Properties/Settings.Designer.cs b/Samples/DurableServer/Properties/Settings.Designer.cs new file mode 100644 index 0000000..41a0d0f --- /dev/null +++ b/Samples/DurableServer/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DurableServer.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Samples/DurableServer/Properties/Settings.settings b/Samples/DurableServer/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/Samples/DurableServer/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Samples/ImageClient/Form1.Designer.cs b/Samples/ImageClient/Form1.Designer.cs new file mode 100644 index 0000000..4525eb6 --- /dev/null +++ b/Samples/ImageClient/Form1.Designer.cs @@ -0,0 +1,87 @@ +namespace ImageClient +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(12, 12); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(146, 22); + this.textBox1.TabIndex = 0; + this.textBox1.Text = "localhost"; + // + // button1 + // + this.button1.Location = new System.Drawing.Point(164, 12); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(161, 23); + this.button1.TabIndex = 1; + this.button1.Text = "Spawn imagegetter"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // button2 + // + this.button2.Location = new System.Drawing.Point(331, 12); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(129, 23); + this.button2.TabIndex = 2; + this.button2.Text = "Settings"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(469, 44); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "Form1"; + this.Text = "Image client"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + } +} + diff --git a/Samples/ImageClient/Form1.cs b/Samples/ImageClient/Form1.cs new file mode 100644 index 0000000..d25420d --- /dev/null +++ b/Samples/ImageClient/Form1.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using Lidgren.Network; +using SamplesCommon; + +namespace ImageClient +{ + public partial class Form1 : Form + { + private NetPeer m_dummyPeer; + private NetPeerConfiguration m_config; + + public Form1() + { + m_config = new NetPeerConfiguration("ImageTransfer"); + m_dummyPeer = new NetPeer(m_config); + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + Program.SpawnGetter(textBox1.Text, m_config); + } + + private void button2_Click(object sender, EventArgs e) + { + NetPeerSettingsWindow win = new NetPeerSettingsWindow("Client settings", m_dummyPeer); + win.Show(); + } + } +} diff --git a/Samples/ImageClient/Form1.resx b/Samples/ImageClient/Form1.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/Samples/ImageClient/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ImageClient/ImageClient.csproj b/Samples/ImageClient/ImageClient.csproj new file mode 100644 index 0000000..9070c75 --- /dev/null +++ b/Samples/ImageClient/ImageClient.csproj @@ -0,0 +1,110 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {69E64B8C-4736-4334-87BF-DD631A3AD144} + WinExe + Properties + ImageClient + ImageClient + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + + + + Form + + + Form1.cs + + + Form + + + ImageGetter.cs + + + + + Form1.cs + Designer + + + ImageGetter.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + {773069DA-B66E-4667-ADCB-0D215AD8CF3E} + SamplesCommon + + + + + \ No newline at end of file diff --git a/Samples/ImageClient/ImageGetter.Designer.cs b/Samples/ImageClient/ImageGetter.Designer.cs new file mode 100644 index 0000000..cdf9e53 --- /dev/null +++ b/Samples/ImageClient/ImageGetter.Designer.cs @@ -0,0 +1,72 @@ +namespace ImageClient +{ + partial class ImageGetter + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // pictureBox1 + // + this.pictureBox1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.pictureBox1.Location = new System.Drawing.Point(12, 134); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(260, 238); + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + // + // richTextBox1 + // + this.richTextBox1.Location = new System.Drawing.Point(12, 12); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(562, 116); + this.richTextBox1.TabIndex = 1; + this.richTextBox1.Text = ""; + // + // ImageGetter + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(586, 521); + this.Controls.Add(this.richTextBox1); + this.Controls.Add(this.pictureBox1); + this.Name = "ImageGetter"; + this.Text = "ImageGetter"; + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.PictureBox pictureBox1; + public System.Windows.Forms.RichTextBox richTextBox1; + } +} \ No newline at end of file diff --git a/Samples/ImageClient/ImageGetter.cs b/Samples/ImageClient/ImageGetter.cs new file mode 100644 index 0000000..591457f --- /dev/null +++ b/Samples/ImageClient/ImageGetter.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using Lidgren.Network; +using SamplesCommon; + +namespace ImageClient +{ + public partial class ImageGetter : Form + { + public NetClient Client; + public byte[] Buffer = new byte[990]; + public bool[] ReceivedSegments; + public int NumReceivedSegments; + + public ImageGetter(string host, NetPeerConfiguration copyConfig) + { + InitializeComponent(); + + NetPeerConfiguration config = copyConfig.Clone(); + config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse); + + Client = new NetClient(config); + Client.Start(); + + Client.DiscoverLocalPeers(14242); + } + + public void Heartbeat() + { + NetIncomingMessage inc; + while ((inc = Client.ReadMessage()) != null) + { + switch(inc.MessageType) + { + case NetIncomingMessageType.DiscoveryResponse: + // found server! just connect... + + string serverResponseHello = inc.ReadString(); + + // create approval data + NetOutgoingMessage approval = Client.CreateMessage(); + approval.Write(42); + approval.Write("secret"); + + Client.Connect(inc.SenderEndpoint, approval); + break; + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.VerboseDebugMessage: + case NetIncomingMessageType.WarningMessage: + case NetIncomingMessageType.ErrorMessage: + string str = inc.ReadString(); + NativeMethods.AppendText(richTextBox1, str); + System.IO.File.AppendAllText("C:\\tmp\\clientlog.txt", str + Environment.NewLine); + break; + case NetIncomingMessageType.StatusChanged: + NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte(); + string reason = inc.ReadString(); + NativeMethods.AppendText(richTextBox1, "New status: " + status + " (" + reason + ")"); + break; + case NetIncomingMessageType.Data: + // image data, whee! + // ineffective but simple data model + ushort width = inc.ReadUInt16(); + ushort height = inc.ReadUInt16(); + uint segment = inc.ReadVariableUInt32(); + + Bitmap bm = pictureBox1.Image as Bitmap; + if (bm == null) + { + bm = new Bitmap(width + 1, height + 1); + pictureBox1.Image = bm; + this.Size = new System.Drawing.Size(width + 40, height + 60); + pictureBox1.SetBounds(12, 12, width, height); + } + pictureBox1.SuspendLayout(); + + int totalBytes = (width * height * 3); + if (inc.LengthBytes < totalBytes) + { + int wholeSegments = totalBytes / 990; + int segLen = 990; + int remainder = totalBytes - (wholeSegments * inc.LengthBytes); + int totalNumberOfSegments = wholeSegments + (remainder > 0 ? 1 : 0); + if (segment >= wholeSegments) + segLen = remainder; // last segment can be shorter + + if (ReceivedSegments == null) + ReceivedSegments = new bool[totalNumberOfSegments]; + if (ReceivedSegments[segment] == false) + { + ReceivedSegments[segment] = true; + NumReceivedSegments++; + if (NumReceivedSegments >= totalNumberOfSegments) + { + Client.Disconnect("So long and thanks for all the fish!"); + } + } + + + + int pixelsAhead = (int)segment * 330; + + int y = pixelsAhead / width; + int x = pixelsAhead - (y * width); + + for (int i = 0; i < (segLen / 3); i++) + { + // set pixel + byte r = inc.ReadByte(); + byte g = inc.ReadByte(); + byte b = inc.ReadByte(); + Color col = Color.FromArgb(r, g, b); + bm.SetPixel(x, y, col); + x++; + if (x >= width) + { + x = 0; + y++; + } + } + } + else + { + + for(int y=0;y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ImageClient/Program.cs b/Samples/ImageClient/Program.cs new file mode 100644 index 0000000..a3a42a0 --- /dev/null +++ b/Samples/ImageClient/Program.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; +using SamplesCommon; +using Lidgren.Network; + +namespace ImageClient +{ + static class Program + { + public static Form1 MainForm; + public static List Getters = new List(); + + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + MainForm = new Form1(); + + Application.Idle += new EventHandler(AppLoop); + Application.Run(MainForm); + } + + static void AppLoop(object sender, EventArgs e) + { + foreach (ImageGetter getter in Getters) + getter.Text = "Client; " + getter.Client.Statistics.BytesAllocated + " bytes allocated"; + + while (NativeMethods.AppStillIdle) + { + foreach (ImageGetter getter in Getters) + getter.Heartbeat(); + System.Threading.Thread.Sleep(1); + } + } + + internal static void SpawnGetter(string host, NetPeerConfiguration copyConfig) + { + ImageGetter getter = new ImageGetter(host, copyConfig); + Getters.Add(getter); + getter.Show(); + } + } +} diff --git a/Samples/ImageClient/Properties/AssemblyInfo.cs b/Samples/ImageClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9465f55 --- /dev/null +++ b/Samples/ImageClient/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("ImageClient")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8dc39d59-b635-4296-bdab-3a18e2d9f425")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/ImageClient/Properties/Resources.Designer.cs b/Samples/ImageClient/Properties/Resources.Designer.cs new file mode 100644 index 0000000..7afb00a --- /dev/null +++ b/Samples/ImageClient/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ImageClient.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ImageClient.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Samples/ImageClient/Properties/Resources.resx b/Samples/ImageClient/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Samples/ImageClient/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ImageClient/Properties/Settings.Designer.cs b/Samples/ImageClient/Properties/Settings.Designer.cs new file mode 100644 index 0000000..ff27b62 --- /dev/null +++ b/Samples/ImageClient/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ImageClient.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Samples/ImageClient/Properties/Settings.settings b/Samples/ImageClient/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/Samples/ImageClient/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Samples/ImageServer/Form1.Designer.cs b/Samples/ImageServer/Form1.Designer.cs new file mode 100644 index 0000000..737777a --- /dev/null +++ b/Samples/ImageServer/Form1.Designer.cs @@ -0,0 +1,89 @@ +namespace ImageServer +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.SuspendLayout(); + // + // button1 + // + this.button1.Location = new System.Drawing.Point(253, 12); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 0; + this.button1.Text = "settings"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // button2 + // + this.button2.Location = new System.Drawing.Point(12, 12); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(235, 23); + this.button2.TabIndex = 1; + this.button2.Text = "Select image and start serving"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // richTextBox1 + // + this.richTextBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.richTextBox1.Location = new System.Drawing.Point(12, 41); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(469, 130); + this.richTextBox1.TabIndex = 2; + this.richTextBox1.Text = ""; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(493, 183); + this.Controls.Add(this.richTextBox1); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "Form1"; + this.Text = "Server: Not running"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + public System.Windows.Forms.RichTextBox richTextBox1; + } +} + diff --git a/Samples/ImageServer/Form1.cs b/Samples/ImageServer/Form1.cs new file mode 100644 index 0000000..e4b0c65 --- /dev/null +++ b/Samples/ImageServer/Form1.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using SamplesCommon; + +namespace ImageServer +{ + public partial class Form1 : Form + { + private NetPeerSettingsWindow m_settingsWindow; + + public Form1() + { + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + if (m_settingsWindow == null) + { + m_settingsWindow = new NetPeerSettingsWindow("Image server settings", Program.Server); + m_settingsWindow.Show(); + } + else + { + m_settingsWindow.Close(); + m_settingsWindow = null; + } + } + + private void button2_Click(object sender, EventArgs e) + { + OpenFileDialog dlg = new OpenFileDialog(); + dlg.Filter = "Image files|*.png;*.jpg;*.jpeg"; + DialogResult res = dlg.ShowDialog(); + if (res != DialogResult.OK) + return; + Program.Start(dlg.FileName); + } + } +} diff --git a/Samples/ImageServer/Form1.resx b/Samples/ImageServer/Form1.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/Samples/ImageServer/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ImageServer/ImageServer.csproj b/Samples/ImageServer/ImageServer.csproj new file mode 100644 index 0000000..c9d047e --- /dev/null +++ b/Samples/ImageServer/ImageServer.csproj @@ -0,0 +1,93 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {36382EFB-BE9E-45B3-BEC8-E70F65CDF868} + WinExe + Properties + ImageServer + ImageServer + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + {773069DA-B66E-4667-ADCB-0D215AD8CF3E} + SamplesCommon + + + + + \ No newline at end of file diff --git a/Samples/ImageServer/Program.cs b/Samples/ImageServer/Program.cs new file mode 100644 index 0000000..5c637d7 --- /dev/null +++ b/Samples/ImageServer/Program.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +using Lidgren.Network; +using SamplesCommon; +using System.Drawing; + +namespace ImageServer +{ + static class Program + { + public static Form1 MainForm; + public static NetServer Server; + public static byte[] ImageData; + public static int ImageWidth, ImageHeight; + + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + MainForm = new Form1(); + + // create a configuration, use identifier "ImageTransfer" - same as client + NetPeerConfiguration config = new NetPeerConfiguration("ImageTransfer"); + config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); + config.EnableMessageType(NetIncomingMessageType.DiscoveryRequest); + + // listen on port 14242 + config.Port = 14242; + + Server = new NetServer(config); + + + Application.Idle += new EventHandler(AppLoop); + Application.Run(MainForm); + } + + static void AppLoop(object sender, EventArgs e) + { + NetIncomingMessage inc; + + MainForm.Text = "Server; " + Server.Statistics.BytesAllocated + " bytes allocated"; + + while (NativeMethods.AppStillIdle) + { + // read any pending messages + while ((inc = Server.WaitMessage(100)) != null) + { + switch (inc.MessageType) + { + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.VerboseDebugMessage: + case NetIncomingMessageType.WarningMessage: + case NetIncomingMessageType.ErrorMessage: + // just print any message + string str = inc.ReadString(); + NativeMethods.AppendText(MainForm.richTextBox1, str); + System.IO.File.AppendAllText("C:\\tmp\\serverlog.txt", str + Environment.NewLine); + break; + case NetIncomingMessageType.DiscoveryRequest: + NetOutgoingMessage dom = Server.CreateMessage(); + dom.Write("Kokosboll"); + Server.SendDiscoveryResponse(dom, inc.SenderEndpoint); + break; + case NetIncomingMessageType.ConnectionApproval: + + // Here we could check inc.SenderConnection.RemoteEndPoint, deny certain ip + + // check hail data + try + { + int a = inc.ReadInt32(); + string s = inc.ReadString(); + + if (a == 42 && s == "secret") + inc.SenderConnection.Approve(); + else + inc.SenderConnection.Deny("Bad approve data, go away!"); + } + catch (NetException) + { + inc.SenderConnection.Deny("Bad approve data, go away!"); + } + break; + case NetIncomingMessageType.StatusChanged: + NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte(); + NativeMethods.AppendText(MainForm.richTextBox1, "New status: " + status + " (" + inc.ReadString() + ")"); + if (status == NetConnectionStatus.Connected) + { + // + // A client connected; send the entire image in chunks of 990 bytes + // + /* + uint seg = 0; + int ptr = 0; + while (ptr < ImageData.Length) + { + int l = ImageData.Length - ptr > 990 ? 990 : ImageData.Length - ptr; + NetOutgoingMessage om = Server.CreateMessage(l); + om.Write((ushort)ImageWidth); + om.Write((ushort)ImageHeight); + om.WriteVariableUInt32(seg++); + om.Write(ImageData, ptr, l); + ptr += 990; + + Server.SendMessage(om, inc.SenderConnection, NetDeliveryMethod.ReliableUnordered, 0); + } + */ + + NetOutgoingMessage om = Server.CreateMessage(ImageData.Length); + + om.Write((ushort)ImageWidth); + om.Write((ushort)ImageHeight); + om.WriteVariableUInt32(0); + + // send entire as a large message that will be automatically fragmented by the library + om.Write(ImageData); + + Server.SendMessage(om, inc.SenderConnection, NetDeliveryMethod.ReliableUnordered, 0); + + // all messages will be sent before disconnect so we can call it here + // inc.SenderConnection.Disconnect("Bye bye now"); + } + break; + } + + // recycle message to avoid garbage + Server.Recycle(inc); + } + } + } + + public static void Start(string filename) + { + if (Server.Status != NetPeerStatus.NotRunning) + { + Server.Shutdown("Restarting"); + System.Threading.Thread.Sleep(100); + } + + Server.Start(); + + MainForm.Text = "Server: Running"; + + // get image size + Bitmap bm = Bitmap.FromFile(filename) as Bitmap; + ImageWidth = bm.Width; + ImageHeight = bm.Height; + + // extract color bytes + // very slow method, but small code size + ImageData = new byte[3 * ImageWidth * ImageHeight]; + int ptr = 0; + for (int y = 0; y < ImageHeight; y++) + { + for (int x = 0; x < ImageWidth; x++) + { + Color color = bm.GetPixel(x, y); + ImageData[ptr++] = color.R; + ImageData[ptr++] = color.G; + ImageData[ptr++] = color.B; + } + } + + bm.Dispose(); + } + } +} diff --git a/Samples/ImageServer/Properties/AssemblyInfo.cs b/Samples/ImageServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5b32b04 --- /dev/null +++ b/Samples/ImageServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("ImageServer")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4c8071cf-f3d6-4d3f-8937-944273b6fb40")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/ImageServer/Properties/Resources.Designer.cs b/Samples/ImageServer/Properties/Resources.Designer.cs new file mode 100644 index 0000000..84d02b2 --- /dev/null +++ b/Samples/ImageServer/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ImageServer.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ImageServer.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Samples/ImageServer/Properties/Resources.resx b/Samples/ImageServer/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Samples/ImageServer/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/ImageServer/Properties/Settings.Designer.cs b/Samples/ImageServer/Properties/Settings.Designer.cs new file mode 100644 index 0000000..4008204 --- /dev/null +++ b/Samples/ImageServer/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ImageServer.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Samples/ImageServer/Properties/Settings.settings b/Samples/ImageServer/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/Samples/ImageServer/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Samples/SamplesCommon/NativeMethods.cs b/Samples/SamplesCommon/NativeMethods.cs new file mode 100644 index 0000000..64a3a01 --- /dev/null +++ b/Samples/SamplesCommon/NativeMethods.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace SamplesCommon +{ + public static class NativeMethods + { + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); + public const int WM_VSCROLL = 277; // Vertical scroll + public const int SB_BOTTOM = 7; // Scroll to bottom + + [StructLayout(LayoutKind.Sequential)] + public struct PeekMsg + { + public IntPtr hWnd; + public Message msg; + public IntPtr wParam; + public IntPtr lParam; + public uint time; + public System.Drawing.Point p; + } + + [System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously + [DllImport("User32.dll", CharSet = CharSet.Auto)] + public static extern bool PeekMessage(out PeekMsg msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags); + + public static bool AppStillIdle + { + get + { + PeekMsg msg; + return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); + } + } + + public static void AppendText(RichTextBox box, string line) + { + try + { + box.AppendText(line + Environment.NewLine); + ScrollRichTextBox(box); + } + catch + { + } + } + + public static void ScrollRichTextBox(RichTextBox box) + { + if (box == null || box.IsDisposed || box.Disposing) + return; + SendMessage(box.Handle, WM_VSCROLL, (IntPtr)SB_BOTTOM, IntPtr.Zero); + } + } +} diff --git a/Samples/SamplesCommon/NetPeerSettingsWindow.Designer.cs b/Samples/SamplesCommon/NetPeerSettingsWindow.Designer.cs new file mode 100644 index 0000000..2d6823b --- /dev/null +++ b/Samples/SamplesCommon/NetPeerSettingsWindow.Designer.cs @@ -0,0 +1,362 @@ +namespace SamplesCommon +{ + partial class NetPeerSettingsWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.DebugCheckBox = new System.Windows.Forms.CheckBox(); + this.VerboseCheckBox = new System.Windows.Forms.CheckBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.label10 = new System.Windows.Forms.Label(); + this.label11 = new System.Windows.Forms.Label(); + this.ThrottleTextBox = new System.Windows.Forms.TextBox(); + this.label8 = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.LossTextBox = new System.Windows.Forms.TextBox(); + this.MinLatencyTextBox = new System.Windows.Forms.TextBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.label7 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.StatisticsLabel = new System.Windows.Forms.Label(); + this.button2 = new System.Windows.Forms.Button(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.SuspendLayout(); + // + // DebugCheckBox + // + this.DebugCheckBox.AutoSize = true; + this.DebugCheckBox.Location = new System.Drawing.Point(6, 21); + this.DebugCheckBox.Name = "DebugCheckBox"; + this.DebugCheckBox.Size = new System.Drawing.Size(153, 17); + this.DebugCheckBox.TabIndex = 0; + this.DebugCheckBox.Text = "Display Debug messages"; + this.DebugCheckBox.UseVisualStyleBackColor = true; + this.DebugCheckBox.CheckedChanged += new System.EventHandler(this.DebugCheckBox_CheckedChanged); + // + // VerboseCheckBox + // + this.VerboseCheckBox.AutoSize = true; + this.VerboseCheckBox.Location = new System.Drawing.Point(6, 44); + this.VerboseCheckBox.Name = "VerboseCheckBox"; + this.VerboseCheckBox.Size = new System.Drawing.Size(197, 17); + this.VerboseCheckBox.TabIndex = 1; + this.VerboseCheckBox.Text = "Display Verbose debug messages"; + this.VerboseCheckBox.UseVisualStyleBackColor = true; + this.VerboseCheckBox.CheckedChanged += new System.EventHandler(this.VerboseCheckBox_CheckedChanged); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.label10); + this.groupBox1.Controls.Add(this.label11); + this.groupBox1.Controls.Add(this.ThrottleTextBox); + this.groupBox1.Controls.Add(this.label8); + this.groupBox1.Controls.Add(this.label9); + this.groupBox1.Controls.Add(this.textBox2); + this.groupBox1.Controls.Add(this.label5); + this.groupBox1.Controls.Add(this.label4); + this.groupBox1.Controls.Add(this.textBox3); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Controls.Add(this.label2); + this.groupBox1.Controls.Add(this.label1); + this.groupBox1.Controls.Add(this.LossTextBox); + this.groupBox1.Controls.Add(this.MinLatencyTextBox); + this.groupBox1.Location = new System.Drawing.Point(291, 12); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(300, 142); + this.groupBox1.TabIndex = 2; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Simulation"; + // + // label10 + // + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(163, 108); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(75, 13); + this.label10.TabIndex = 13; + this.label10.Text = "bytes/second"; + // + // label11 + // + this.label11.AutoSize = true; + this.label11.Location = new System.Drawing.Point(6, 108); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(47, 13); + this.label11.TabIndex = 12; + this.label11.Text = "Throttle"; + // + // ThrottleTextBox + // + this.ThrottleTextBox.Location = new System.Drawing.Point(103, 105); + this.ThrottleTextBox.Name = "ThrottleTextBox"; + this.ThrottleTextBox.Size = new System.Drawing.Size(54, 22); + this.ThrottleTextBox.TabIndex = 11; + this.ThrottleTextBox.TextChanged += new System.EventHandler(this.ThrottleTextBox_TextChanged); + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(163, 80); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(16, 13); + this.label8.TabIndex = 10; + this.label8.Text = "%"; + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(6, 80); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(61, 13); + this.label9.TabIndex = 9; + this.label9.Text = "Duplicates"; + // + // textBox2 + // + this.textBox2.Location = new System.Drawing.Point(103, 77); + this.textBox2.Name = "textBox2"; + this.textBox2.Size = new System.Drawing.Size(54, 22); + this.textBox2.TabIndex = 8; + this.textBox2.TextChanged += new System.EventHandler(this.textBox2_TextChanged); + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(163, 52); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(16, 13); + this.label5.TabIndex = 7; + this.label5.Text = "%"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(247, 24); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(21, 13); + this.label4.TabIndex = 6; + this.label4.Text = "ms"; + // + // textBox3 + // + this.textBox3.Location = new System.Drawing.Point(185, 21); + this.textBox3.Name = "textBox3"; + this.textBox3.Size = new System.Drawing.Size(54, 22); + this.textBox3.TabIndex = 5; + this.textBox3.TextChanged += new System.EventHandler(this.textBox3_TextChanged); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(163, 24); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(18, 13); + this.label3.TabIndex = 4; + this.label3.Text = "to"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(6, 52); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(29, 13); + this.label2.TabIndex = 3; + this.label2.Text = "Loss"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(6, 24); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(91, 13); + this.label1.TabIndex = 2; + this.label1.Text = "One way latency"; + // + // LossTextBox + // + this.LossTextBox.Location = new System.Drawing.Point(103, 49); + this.LossTextBox.Name = "LossTextBox"; + this.LossTextBox.Size = new System.Drawing.Size(54, 22); + this.LossTextBox.TabIndex = 1; + this.LossTextBox.TextChanged += new System.EventHandler(this.LossTextBox_TextChanged); + // + // MinLatencyTextBox + // + this.MinLatencyTextBox.Location = new System.Drawing.Point(103, 21); + this.MinLatencyTextBox.Name = "MinLatencyTextBox"; + this.MinLatencyTextBox.Size = new System.Drawing.Size(54, 22); + this.MinLatencyTextBox.TabIndex = 0; + this.MinLatencyTextBox.TextChanged += new System.EventHandler(this.MinLatencyTextBox_TextChanged); + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.label7); + this.groupBox2.Controls.Add(this.DebugCheckBox); + this.groupBox2.Controls.Add(this.VerboseCheckBox); + this.groupBox2.Controls.Add(this.label6); + this.groupBox2.Controls.Add(this.textBox1); + this.groupBox2.Location = new System.Drawing.Point(12, 12); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(273, 142); + this.groupBox2.TabIndex = 3; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Settings"; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(182, 70); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(21, 13); + this.label7.TabIndex = 10; + this.label7.Text = "ms"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(6, 70); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(84, 13); + this.label6.TabIndex = 9; + this.label6.Text = "Ping frequency"; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(98, 67); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(76, 22); + this.textBox1.TabIndex = 8; + this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged); + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button1.Location = new System.Drawing.Point(494, 367); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(101, 36); + this.button1.TabIndex = 5; + this.button1.Text = "Refresh"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // groupBox3 + // + this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox3.Controls.Add(this.StatisticsLabel); + this.groupBox3.Location = new System.Drawing.Point(12, 160); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(579, 197); + this.groupBox3.TabIndex = 6; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "Statistics"; + // + // StatisticsLabel + // + this.StatisticsLabel.AutoSize = true; + this.StatisticsLabel.Location = new System.Drawing.Point(6, 22); + this.StatisticsLabel.Name = "StatisticsLabel"; + this.StatisticsLabel.Size = new System.Drawing.Size(79, 13); + this.StatisticsLabel.TabIndex = 0; + this.StatisticsLabel.Text = "StatisticsLabel"; + // + // button2 + // + this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button2.Location = new System.Drawing.Point(389, 367); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(99, 36); + this.button2.TabIndex = 7; + this.button2.Text = "Close"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // NetPeerSettingsWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(603, 411); + this.Controls.Add(this.button2); + this.Controls.Add(this.groupBox3); + this.Controls.Add(this.button1); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Name = "NetPeerSettingsWindow"; + this.Text = "NetPeerSettingsWindow1"; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label3; + public System.Windows.Forms.CheckBox DebugCheckBox; + public System.Windows.Forms.CheckBox VerboseCheckBox; + public System.Windows.Forms.TextBox LossTextBox; + public System.Windows.Forms.TextBox MinLatencyTextBox; + public System.Windows.Forms.TextBox textBox3; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.GroupBox groupBox3; + public System.Windows.Forms.Label StatisticsLabel; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Label label9; + public System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label label11; + public System.Windows.Forms.TextBox ThrottleTextBox; + } +} \ No newline at end of file diff --git a/Samples/SamplesCommon/NetPeerSettingsWindow.cs b/Samples/SamplesCommon/NetPeerSettingsWindow.cs new file mode 100644 index 0000000..4646015 --- /dev/null +++ b/Samples/SamplesCommon/NetPeerSettingsWindow.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using System.Reflection; + +using Lidgren.Network; + +namespace SamplesCommon +{ + public partial class NetPeerSettingsWindow : Form + { + public NetPeer Peer; + public Timer timer; + + public NetPeerSettingsWindow(string title, NetPeer peer) + { + Peer = peer; + InitializeComponent(); + RefreshData(); + this.Text = title; + + // auto refresh now and then + timer = new Timer(); + timer.Interval = 250; + timer.Tick += new EventHandler(timer_Tick); + timer.Enabled = true; + } + + protected override void OnClosed(EventArgs e) + { + timer.Enabled = false; + base.OnClosed(e); + } + + void timer_Tick(object sender, EventArgs e) + { + RefreshData(); + } + + private void RefreshData() + { +#if DEBUG + LossTextBox.Text = ((int)(Peer.Configuration.SimulatedLoss * 100)).ToString(); + textBox2.Text = ((int)(Peer.Configuration.SimulatedDuplicatesChance * 100)).ToString(); + MinLatencyTextBox.Text = ((int)(Peer.Configuration.SimulatedMinimumLatency * 1000)).ToString(); + textBox3.Text = ((int)((Peer.Configuration.SimulatedMinimumLatency + Peer.Configuration.SimulatedRandomLatency) * 1000)).ToString(); +#else + LossTextBox.Text = "0"; + MinLatencyTextBox.Text = "0"; + textBox3.Text = "0"; + textBox2.Text = "0"; +#endif + DebugCheckBox.Checked = Peer.Configuration.IsMessageTypeEnabled(NetIncomingMessageType.DebugMessage); + VerboseCheckBox.Checked = Peer.Configuration.IsMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage); + textBox1.Text = (Peer.Configuration.PingFrequency * 1000).ToString(); + ThrottleTextBox.Text = Peer.Configuration.ThrottleBytesPerSecond.ToString(); + + StringBuilder bdr = new StringBuilder(); + bdr.AppendLine(Peer.Statistics.ToString()); + + if (Peer.ConnectionsCount > 0) + { + NetConnection conn = Peer.Connections[0]; + bdr.AppendLine("Connection 0:"); + bdr.AppendLine("Average RTT: " + ((int)(conn.AverageRoundtripTime * 1000.0f)) + " ms"); + bdr.Append(conn.Statistics.ToString()); + } + + StatisticsLabel.Text = bdr.ToString(); + } + + private void DebugCheckBox_CheckedChanged(object sender, EventArgs e) + { + Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.DebugMessage, DebugCheckBox.Checked); + } + + private void VerboseCheckBox_CheckedChanged(object sender, EventArgs e) + { + Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage, VerboseCheckBox.Checked); + } + + private void LossTextBox_TextChanged(object sender, EventArgs e) + { +#if DEBUG + float ms; + if (Single.TryParse(LossTextBox.Text, out ms)) + Peer.Configuration.SimulatedLoss = (float)((double)ms / 100.0); +#endif + } + + private void textBox2_TextChanged(object sender, EventArgs e) + { +#if DEBUG + float ms; + if (Single.TryParse(textBox2.Text, out ms)) + Peer.Configuration.SimulatedDuplicatesChance = (float)((double)ms / 100.0); +#endif + } + + private void MinLatencyTextBox_TextChanged(object sender, EventArgs e) + { +#if DEBUG + float min; + if (float.TryParse(MinLatencyTextBox.Text, out min)) + Peer.Configuration.SimulatedMinimumLatency = (float)(min / 1000.0); + MinLatencyTextBox.Text = ((int)(Peer.Configuration.SimulatedMinimumLatency * 1000)).ToString(); +#endif + } + + private void textBox3_TextChanged(object sender, EventArgs e) + { +#if DEBUG + float max; + if (float.TryParse(textBox3.Text, out max)) + { + max = (float)((double)max / 1000.0); + float r = max - Peer.Configuration.SimulatedMinimumLatency; + if (r > 0) + { + Peer.Configuration.SimulatedRandomLatency = r; + double nm = (double)Peer.Configuration.SimulatedMinimumLatency + (double)Peer.Configuration.SimulatedRandomLatency; + textBox3.Text = ((int)(max * 1000)).ToString(); + } + } +#endif + } + + private void button1_Click(object sender, EventArgs e) + { + RefreshData(); + } + + private void textBox1_TextChanged(object sender, EventArgs e) + { + float d; + if (float.TryParse(textBox1.Text, out d)) + Peer.Configuration.PingFrequency = (float)(d / 1000.0); + } + + private void button2_Click(object sender, EventArgs e) + { + this.Close(); + } + + private void ThrottleTextBox_TextChanged(object sender, EventArgs e) + { + uint bps; + if (UInt32.TryParse(ThrottleTextBox.Text, out bps)) + Peer.Configuration.ThrottleBytesPerSecond = (int)bps; + } + + } +} diff --git a/Samples/SamplesCommon/NetPeerSettingsWindow.resx b/Samples/SamplesCommon/NetPeerSettingsWindow.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/Samples/SamplesCommon/NetPeerSettingsWindow.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/SamplesCommon/Properties/AssemblyInfo.cs b/Samples/SamplesCommon/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f121a97 --- /dev/null +++ b/Samples/SamplesCommon/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SamplesCommon")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("SamplesCommon")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d1622f4-1dc7-4e94-8ec0-0a2a295dcf9a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/SamplesCommon/SamplesCommon.csproj b/Samples/SamplesCommon/SamplesCommon.csproj new file mode 100644 index 0000000..07b30da --- /dev/null +++ b/Samples/SamplesCommon/SamplesCommon.csproj @@ -0,0 +1,72 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {773069DA-B66E-4667-ADCB-0D215AD8CF3E} + Library + Properties + SamplesCommon + SamplesCommon + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + Form + + + NetPeerSettingsWindow.cs + + + + + + NetPeerSettingsWindow.cs + Designer + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + + + \ No newline at end of file diff --git a/Samples/XNA sample/XnaGameClient.sln b/Samples/XNA sample/XnaGameClient.sln new file mode 100644 index 0000000..bfbfeeb --- /dev/null +++ b/Samples/XNA sample/XnaGameClient.sln @@ -0,0 +1,60 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XnaGameClient", "XnaGameClient\XnaGameClient.csproj", "{D084E1C2-CE6B-42A5-918E-37EC7D2B5132}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XnaGameServer", "XnaGameServer\XnaGameServer.csproj", "{2A16EEC3-99B7-45D8-B185-6E1101225540}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lidgren.Network", "..\..\Lidgren.Network\Lidgren.Network.csproj", "{FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Debug|Any CPU.ActiveCfg = Debug|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Debug|x86.ActiveCfg = Debug|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Debug|x86.Build.0 = Debug|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Release|Any CPU.ActiveCfg = Release|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Release|Mixed Platforms.Build.0 = Release|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Release|x86.ActiveCfg = Release|x86 + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132}.Release|x86.Build.0 = Release|x86 + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Debug|x86.ActiveCfg = Debug|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Release|Any CPU.Build.0 = Release|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2A16EEC3-99B7-45D8-B185-6E1101225540}.Release|x86.ActiveCfg = Release|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Debug|x86.ActiveCfg = Debug|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Release|Any CPU.Build.0 = Release|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD}.Release|x86.ActiveCfg = Release|Any CPU + {416F5A82-22D8-4A94-BC19-4C6A24F34A73}.Debug|Any CPU.ActiveCfg = Debug|x86 + {416F5A82-22D8-4A94-BC19-4C6A24F34A73}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {416F5A82-22D8-4A94-BC19-4C6A24F34A73}.Debug|x86.ActiveCfg = Debug|x86 + {416F5A82-22D8-4A94-BC19-4C6A24F34A73}.Release|Any CPU.ActiveCfg = Release|x86 + {416F5A82-22D8-4A94-BC19-4C6A24F34A73}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {416F5A82-22D8-4A94-BC19-4C6A24F34A73}.Release|x86.ActiveCfg = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Samples/XNA sample/XnaGameClient/Content/Content.contentproj b/Samples/XNA sample/XnaGameClient/Content/Content.contentproj new file mode 100644 index 0000000..d524091 --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/Content/Content.contentproj @@ -0,0 +1,75 @@ + + + 416f5a82-22d8-4a94-bc19-4c6a24f34a73 + {96E2B04D-8817-42c6-938A-82C39BA4D311};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Debug + x86 + Library + Properties + v3.5 + v3.1 + x86 + bin\$(Platform)\$(Configuration) + + + Windows + + + Windows + + + + False + + + False + + + False + + + False + + + False + + + False + + + + + c1 + TextureImporter + TextureProcessor + + + c2 + TextureImporter + TextureProcessor + + + c3 + TextureImporter + TextureProcessor + + + c4 + TextureImporter + TextureProcessor + + + c5 + TextureImporter + TextureProcessor + + + + + \ No newline at end of file diff --git a/Samples/XNA sample/XnaGameClient/Content/c1.png b/Samples/XNA sample/XnaGameClient/Content/c1.png new file mode 100644 index 0000000..3dc7c29 Binary files /dev/null and b/Samples/XNA sample/XnaGameClient/Content/c1.png differ diff --git a/Samples/XNA sample/XnaGameClient/Content/c2.png b/Samples/XNA sample/XnaGameClient/Content/c2.png new file mode 100644 index 0000000..dc0538b Binary files /dev/null and b/Samples/XNA sample/XnaGameClient/Content/c2.png differ diff --git a/Samples/XNA sample/XnaGameClient/Content/c3.png b/Samples/XNA sample/XnaGameClient/Content/c3.png new file mode 100644 index 0000000..90f2278 Binary files /dev/null and b/Samples/XNA sample/XnaGameClient/Content/c3.png differ diff --git a/Samples/XNA sample/XnaGameClient/Content/c4.png b/Samples/XNA sample/XnaGameClient/Content/c4.png new file mode 100644 index 0000000..baef177 Binary files /dev/null and b/Samples/XNA sample/XnaGameClient/Content/c4.png differ diff --git a/Samples/XNA sample/XnaGameClient/Content/c5.png b/Samples/XNA sample/XnaGameClient/Content/c5.png new file mode 100644 index 0000000..9d9f958 Binary files /dev/null and b/Samples/XNA sample/XnaGameClient/Content/c5.png differ diff --git a/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/Content.contentproj.FileListAbsolute.txt b/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/Content.contentproj.FileListAbsolute.txt new file mode 100644 index 0000000..638af78 --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/Content.contentproj.FileListAbsolute.txt @@ -0,0 +1,2 @@ +D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\XnaGameClient\Content\obj\x86\Debug\ResolveAssemblyReference.cache +D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\Content\obj\x86\Debug\ResolveAssemblyReference.cache diff --git a/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/ContentPipeline.xml b/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/ContentPipeline.xml new file mode 100644 index 0000000..eee5fcc --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/ContentPipeline.xml @@ -0,0 +1,90 @@ + + + + + c1.png + c1 + TextureImporter + TextureProcessor + None + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\bin\x86\Debug\Content\c1.xnb + + + + c2.png + c2 + TextureImporter + TextureProcessor + None + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\bin\x86\Debug\Content\c2.xnb + + + + c3.png + c3 + TextureImporter + TextureProcessor + None + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\bin\x86\Debug\Content\c3.xnb + + + + c4.png + c4 + TextureImporter + TextureProcessor + None + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\bin\x86\Debug\Content\c4.xnb + + + + c5.png + c5 + TextureImporter + TextureProcessor + None + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\bin\x86\Debug\Content\c5.xnb + + + true + + Windows + Debug + false + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\Content\ + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\ + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\Content\obj\x86\Debug\ + D:\dev\GoogleCode\lidgren-network\Generation3\Samples\XNA sample\XnaGameClient\bin\x86\Debug\Content\ + + + + C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v3.1\References\Windows\x86\Microsoft.Xna.Framework.Content.Pipeline.XImporter.dll + 2009-05-28T01:16:14+02:00 + + + C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v3.1\References\Windows\x86\Microsoft.Xna.Framework.Content.Pipeline.VideoImporters.dll + 2009-05-28T01:16:06+02:00 + + + C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v3.1\References\Windows\x86\Microsoft.Xna.Framework.Content.Pipeline.TextureImporter.dll + 2009-05-28T01:16:16+02:00 + + + C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v3.1\References\Windows\x86\Microsoft.Xna.Framework.Content.Pipeline.FBXImporter.dll + 2009-05-28T01:16:26+02:00 + + + C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v3.1\References\Windows\x86\Microsoft.Xna.Framework.Content.Pipeline.EffectImporter.dll + 2009-05-28T01:16:12+02:00 + + + C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v3.1\References\Windows\x86\Microsoft.Xna.Framework.Content.Pipeline.AudioImporters.dll + 2009-05-28T01:16:06+02:00 + + + C:\Windows\assembly\GAC_32\Microsoft.Xna.Framework.Content.Pipeline\3.1.0.0__6d5c3888ef60e27d\Microsoft.Xna.Framework.Content.Pipeline.dll + 2010-01-22T07:58:48.0196058+01:00 + + + + \ No newline at end of file diff --git a/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/ResolveAssemblyReference.cache b/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/ResolveAssemblyReference.cache new file mode 100644 index 0000000..0824e13 Binary files /dev/null and b/Samples/XNA sample/XnaGameClient/Content/obj/x86/Debug/ResolveAssemblyReference.cache differ diff --git a/Samples/XNA sample/XnaGameClient/Game.ico b/Samples/XNA sample/XnaGameClient/Game.ico new file mode 100644 index 0000000..8cff41e Binary files /dev/null and b/Samples/XNA sample/XnaGameClient/Game.ico differ diff --git a/Samples/XNA sample/XnaGameClient/Game1.cs b/Samples/XNA sample/XnaGameClient/Game1.cs new file mode 100644 index 0000000..6700946 --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/Game1.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +using Lidgren.Network; + +namespace XnaGameClient +{ + /// + /// This is the main type for your game + /// + public class Game1 : Microsoft.Xna.Framework.Game + { + GraphicsDeviceManager graphics; + SpriteBatch spriteBatch; + + Texture2D[] textures; + Dictionary positions = new Dictionary(); + NetClient client; + + public Game1() + { + graphics = new GraphicsDeviceManager(this); + Content.RootDirectory = "Content"; + + NetPeerConfiguration config = new NetPeerConfiguration("xnaapp"); + config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse); + + client = new NetClient(config); + client.Start(); + } + + protected override void Initialize() + { + client.DiscoverLocalPeers(14242); + base.Initialize(); + } + + protected override void LoadContent() + { + spriteBatch = new SpriteBatch(GraphicsDevice); + textures = new Texture2D[5]; + for (int i = 0; i < 5; i++) + textures[i] = Content.Load("c" + (i + 1)); + } + + protected override void Update(GameTime gameTime) + { + // + // Collect input + // + int xinput = 0; + int yinput = 0; + KeyboardState keyState = Keyboard.GetState(); + + // exit game if escape or Back is pressed + if (keyState.IsKeyDown(Keys.Escape) || GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) + this.Exit(); + + // use arrows or dpad to move avatar + if (GamePad.GetState(PlayerIndex.One).DPad.Left == ButtonState.Pressed || keyState.IsKeyDown(Keys.Left)) + xinput = -1; + if (GamePad.GetState(PlayerIndex.One).DPad.Right == ButtonState.Pressed || keyState.IsKeyDown(Keys.Right)) + xinput = 1; + if (GamePad.GetState(PlayerIndex.One).DPad.Up == ButtonState.Pressed || keyState.IsKeyDown(Keys.Up)) + yinput = -1; + if (GamePad.GetState(PlayerIndex.One).DPad.Down == ButtonState.Pressed || keyState.IsKeyDown(Keys.Down)) + yinput = 1; + + if (xinput != 0 || yinput != 0) + { + // + // If there's input; send it to server + // + NetOutgoingMessage om = client.CreateMessage(); + om.Write(xinput); // very inefficient to send a full Int32 (4 bytes) but we'll use this for simplicity + om.Write(yinput); + client.SendMessage(om, NetDeliveryMethod.Unreliable); + } + + // read messages + NetIncomingMessage msg; + while ((msg = client.ReadMessage()) != null) + { + switch (msg.MessageType) + { + case NetIncomingMessageType.DiscoveryResponse: + // just connect to first server discovered + client.Connect(msg.SenderEndpoint); + break; + case NetIncomingMessageType.Data: + // server sent a position update + long who = msg.ReadInt64(); + int x = msg.ReadInt32(); + int y = msg.ReadInt32(); + positions[who] = new Vector2(x, y); + break; + } + } + + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(Color.CornflowerBlue); + + spriteBatch.Begin(SpriteBlendMode.AlphaBlend); + + // draw all players + foreach (long who in positions.Keys) + { + // use player unique identifier to choose an image + int num = (int)Math.Abs(who) % 5; + + // draw player + spriteBatch.Draw(textures[num], positions[who], Color.White); + } + + spriteBatch.End(); + + base.Draw(gameTime); + } + + protected override void OnExiting(object sender, EventArgs args) + { + client.Shutdown("bye"); + + base.OnExiting(sender, args); + } + } +} diff --git a/Samples/XNA sample/XnaGameClient/GameThumbnail.png b/Samples/XNA sample/XnaGameClient/GameThumbnail.png new file mode 100644 index 0000000..462311a Binary files /dev/null and b/Samples/XNA sample/XnaGameClient/GameThumbnail.png differ diff --git a/Samples/XNA sample/XnaGameClient/Program.cs b/Samples/XNA sample/XnaGameClient/Program.cs new file mode 100644 index 0000000..a536e98 --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/Program.cs @@ -0,0 +1,19 @@ +using System; + +namespace XnaGameClient +{ + static class Program + { + /// + /// The main entry point for the application. + /// + static void Main(string[] args) + { + using (Game1 game = new Game1()) + { + game.Run(); + } + } + } +} + diff --git a/Samples/XNA sample/XnaGameClient/Properties/AssemblyInfo.cs b/Samples/XNA sample/XnaGameClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..93bde73 --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("XnaGameClient")] +[assembly: AssemblyProduct("XnaGameClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyCompany("Microsoft")] + +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d2337e59-5903-486c-a4f0-4beb3020c965")] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] diff --git a/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj b/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj new file mode 100644 index 0000000..4ab28d6 --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj @@ -0,0 +1,138 @@ + + + {D084E1C2-CE6B-42A5-918E-37EC7D2B5132} + {6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Debug + x86 + WinExe + Properties + XnaGameClient + XnaGameClient + v3.5 + v3.1 + Windows + e7198d36-15ad-4289-ac23-fa03d866c3e5 + Game.ico + GameThumbnail.png + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + bin\x86\Debug + DEBUG;TRACE;WINDOWS + prompt + 4 + true + false + x86 + false + + + pdbonly + true + bin\x86\Release + TRACE;WINDOWS + prompt + 4 + true + false + x86 + true + + + + False + True + + + False + True + + + False + + + False + + + False + + + 3.5 + False + + + + + + + + + + + + + + 416f5a82-22d8-4a94-bc19-4c6a24f34a73 + False + + + + + False + .NET Framework 2.0 %28x86%29 + false + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + true + + + False + Windows Installer 3.1 + true + + + False + Microsoft XNA Framework Redistributable 3.1 + true + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + + + + \ No newline at end of file diff --git a/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj.Debug.cachefile b/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj.Debug.cachefile new file mode 100644 index 0000000..c525e45 --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj.Debug.cachefile @@ -0,0 +1,5 @@ +Content\c1.xnb +Content\c2.xnb +Content\c3.xnb +Content\c4.xnb +Content\c5.xnb diff --git a/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj.user b/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj.user new file mode 100644 index 0000000..86ec92b --- /dev/null +++ b/Samples/XNA sample/XnaGameClient/XnaGameClient.csproj.user @@ -0,0 +1,16 @@ + + + + + + + + + + + + + en-US + false + + \ No newline at end of file diff --git a/Samples/XNA sample/XnaGameServer/Program.cs b/Samples/XNA sample/XnaGameServer/Program.cs new file mode 100644 index 0000000..91bc1a5 --- /dev/null +++ b/Samples/XNA sample/XnaGameServer/Program.cs @@ -0,0 +1,122 @@ +using System; +using System.Threading; + +using Lidgren.Network; + +namespace XnaGameServer +{ + class Program + { + static void Main(string[] args) + { + NetPeerConfiguration config = new NetPeerConfiguration("xnaapp"); + config.EnableMessageType(NetIncomingMessageType.DiscoveryRequest); + config.Port = 14242; + + // create and start server + NetServer server = new NetServer(config); + server.Start(); + + // schedule initial sending of position updates + double nextSendUpdates = NetTime.Now; + + // run until escape is pressed + while (!Console.KeyAvailable || Console.ReadKey().Key != ConsoleKey.Escape) + { + NetIncomingMessage msg; + while ((msg = server.ReadMessage()) != null) + { + switch (msg.MessageType) + { + case NetIncomingMessageType.DiscoveryRequest: + // + // Server received a discovery request from a client; send a discovery response (with no extra data attached) + // + server.SendDiscoveryResponse(null, msg.SenderEndpoint); + break; + case NetIncomingMessageType.VerboseDebugMessage: + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.WarningMessage: + case NetIncomingMessageType.ErrorMessage: + // + // Just print diagnostic messages to console + // + Console.WriteLine(msg.ReadString()); + break; + case NetIncomingMessageType.StatusChanged: + NetConnectionStatus status = (NetConnectionStatus)msg.ReadByte(); + if (status == NetConnectionStatus.Connected) + { + // + // A new player just connected! + // + Console.WriteLine(NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier) + " connected!"); + + // randomize his position and store in connection tag + msg.SenderConnection.Tag = new int[] { + NetRandom.Instance.Next(10, 100), + NetRandom.Instance.Next(10, 100) + }; + } + + break; + case NetIncomingMessageType.Data: + // + // The client sent input to the server + // + int xinput = msg.ReadInt32(); + int yinput = msg.ReadInt32(); + + int[] pos = msg.SenderConnection.Tag as int[]; + + // fancy movement logic goes here; we just append input to position + pos[0] += xinput; + pos[1] += yinput; + break; + } + + // + // send position updates 30 times per second + // + double now = NetTime.Now; + if (now > nextSendUpdates) + { + // Yes, it's time to send position updates + + // for each player... + foreach (NetConnection player in server.Connections) + { + // ... send information about every other player (actually including self) + foreach (NetConnection otherPlayer in server.Connections) + { + // send position update about 'otherPlayer' to 'player' + NetOutgoingMessage om = server.CreateMessage(); + + // write who this position is for + om.Write(otherPlayer.RemoteUniqueIdentifier); + + if (otherPlayer.Tag == null) + otherPlayer.Tag = new int[2]; + + int[] pos = otherPlayer.Tag as int[]; + om.Write(pos[0]); + om.Write(pos[1]); + + // send message + server.SendMessage(om, player, NetDeliveryMethod.Unreliable); + } + } + + // schedule next update + nextSendUpdates += (1.0 / 30.0); + } + } + + // sleep to allow other processes to run smoothly + Thread.Sleep(1); + } + + server.Shutdown("app exiting"); + } + } +} diff --git a/Samples/XNA sample/XnaGameServer/Properties/AssemblyInfo.cs b/Samples/XNA sample/XnaGameServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..91a41d2 --- /dev/null +++ b/Samples/XNA sample/XnaGameServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("XnaGameServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("XnaGameServer")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ad24f34e-32a4-4619-a19f-c16f126d186e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/XNA sample/XnaGameServer/XnaGameServer.csproj b/Samples/XNA sample/XnaGameServer/XnaGameServer.csproj new file mode 100644 index 0000000..beade4f --- /dev/null +++ b/Samples/XNA sample/XnaGameServer/XnaGameServer.csproj @@ -0,0 +1,59 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {2A16EEC3-99B7-45D8-B185-6E1101225540} + Exe + Properties + XnaGameServer + XnaGameServer + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + + + \ No newline at end of file diff --git a/UnitTests/BitVectorTests.cs b/UnitTests/BitVectorTests.cs new file mode 100644 index 0000000..11a6c0d --- /dev/null +++ b/UnitTests/BitVectorTests.cs @@ -0,0 +1,35 @@ +using System; +using Lidgren.Network; + +namespace UnitTests +{ + public static class BitVectorTests + { + public static void Run() + { + NetBitVector v = new NetBitVector(256); + for (int i = 0; i < 256; i++) + { + v.Clear(); + + if (!v.IsEmpty()) + throw new NetException("bit vector fail 1"); + + v.Set(i, true); + + if (v.Get(i) == false) + throw new NetException("bit vector fail 2"); + + if (v.IsEmpty()) + throw new NetException("bit vector fail 3"); + + int f = v.GetFirstSetIndex(); + + if (f != i) + throw new NetException("bit vector fail 4"); + } + + Console.WriteLine("NetBitVector tests OK"); + } + } +} diff --git a/UnitTests/EncryptionTests.cs b/UnitTests/EncryptionTests.cs new file mode 100644 index 0000000..b44501a --- /dev/null +++ b/UnitTests/EncryptionTests.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Lidgren.Network; + +namespace UnitTests +{ + public static class EncryptionTests + { + public static void Run() + { + byte[] salt = NetUtility.ToByteArray("59d7304da9b97e2a9d38"); + byte[] verifier = NetSRP.ComputePasswordVerifier("user", "password", salt); + + Console.WriteLine("Result: " + NetUtility.ToHexString(verifier)); + } + } +} diff --git a/UnitTests/MiscTests.cs b/UnitTests/MiscTests.cs new file mode 100644 index 0000000..6f2c71b --- /dev/null +++ b/UnitTests/MiscTests.cs @@ -0,0 +1,24 @@ +using System; + +using Lidgren.Network; + +namespace UnitTests +{ + public static class MiscTests + { + public static void Run(NetPeer peer) + { + NetPeerConfiguration config = new NetPeerConfiguration("Test"); + + config.EnableMessageType(NetIncomingMessageType.UnconnectedData); + if (config.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData) == false) + throw new NetException("setting enabled message types failed"); + + config.SetMessageTypeEnabled(NetIncomingMessageType.UnconnectedData, false); + if (config.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData) == true) + throw new NetException("setting enabled message types failed"); + + Console.WriteLine("Misc tests OK"); + } + } +} diff --git a/UnitTests/NetQueueTests.cs b/UnitTests/NetQueueTests.cs new file mode 100644 index 0000000..8763423 --- /dev/null +++ b/UnitTests/NetQueueTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using Lidgren.Network; + +namespace UnitTests +{ + public static class NetQueueTests + { + public static void Run() + { + NetQueue queue = new NetQueue(8); + + queue.Enqueue(1); + queue.Enqueue(2); + queue.Enqueue(3); + + if (queue.Count != 3) + throw new Exception("NetQueue failed"); + if (queue.TryDequeue() != 1) + throw new Exception("NetQueue failure"); + if (queue.Count != 2) + throw new Exception("NetQueue failed"); + + queue.EnqueueFirst(42); + if (queue.Count != 3) + throw new Exception("NetQueue failed"); + + if (queue.TryDequeue() != 42) + throw new Exception("NetQueue failed"); + if (queue.TryDequeue() != 2) + throw new Exception("NetQueue failed"); + if (queue.TryDequeue() != 3) + throw new Exception("NetQueue failed"); + if (queue.TryDequeue() != 0) + throw new Exception("NetQueue failed"); + if (queue.TryDequeue() != 0) + throw new Exception("NetQueue failed"); + + queue.Enqueue(78); + if (queue.Count != 1) + throw new Exception("NetQueue failed"); + + if (queue.TryDequeue() != 78) + throw new Exception("NetQueue failed"); + + queue.Clear(); + if (queue.Count != 0) + throw new Exception("NetQueue.Clear failed"); + + Console.WriteLine("NetQueue tests OK"); + } + } +} diff --git a/UnitTests/Program.cs b/UnitTests/Program.cs new file mode 100644 index 0000000..a2cd0c1 --- /dev/null +++ b/UnitTests/Program.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Lidgren.Network; + +namespace UnitTests +{ + class Program + { + static void Main(string[] args) + { + NetPeerConfiguration config = new NetPeerConfiguration("unittests"); + NetPeer peer = new NetPeer(config); + peer.Start(); // needed for initialization + + System.Threading.Thread.Sleep(50); + + Console.WriteLine("Unique identifier is " + NetUtility.ToHexString(peer.UniqueIdentifier)); + + ReadWriteTests.Run(peer); + + NetQueueTests.Run(); + + MiscTests.Run(peer); + + BitVectorTests.Run(); + + EncryptionTests.Run(); + + peer.Shutdown("bye"); + + Console.ReadKey(); + } + } +} diff --git a/UnitTests/Properties/AssemblyInfo.cs b/UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c1feff5 --- /dev/null +++ b/UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("UnitTests")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ea1608b3-3a09-4c31-8ee3-04d8b5943ce2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UnitTests/ReadWriteTests.cs b/UnitTests/ReadWriteTests.cs new file mode 100644 index 0000000..4e28b7b --- /dev/null +++ b/UnitTests/ReadWriteTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; + +using Lidgren.Network; +using System.Reflection; +using System.Text; + +namespace UnitTests +{ + public static class ReadWriteTests + { + public static void Run(NetPeer peer) + { + NetOutgoingMessage msg = peer.CreateMessage(); + + msg.Write(false); + msg.Write(-3, 6); + msg.Write(42); + msg.Write("duke of earl"); + msg.Write((byte)43); + msg.Write((ushort)44); + msg.Write(true); + + msg.WritePadBits(); + + msg.Write(45.0f); + msg.Write(46.0); + msg.WriteVariableInt32(-47); + msg.WriteVariableUInt32(48); + + byte[] data = msg.PeekDataBuffer(); + + NetIncomingMessage inc = (NetIncomingMessage)Activator.CreateInstance(typeof(NetIncomingMessage), true); + typeof(NetIncomingMessage).GetField("m_data", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(inc, data); + typeof(NetIncomingMessage).GetField("m_bitLength", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(inc, msg.LengthBits); + + StringBuilder bdr = new StringBuilder(); + + bdr.Append(inc.ReadBoolean()); + bdr.Append(inc.ReadInt32(6)); + bdr.Append(inc.ReadInt32()); + bdr.Append(inc.ReadString()); + bdr.Append(inc.ReadByte()); + + if (inc.PeekUInt16() != (ushort)44) + throw new NetException("Read/write failure"); + + bdr.Append(inc.ReadUInt16()); + bdr.Append(inc.ReadBoolean()); + + inc.SkipPadBits(); + + bdr.Append(inc.ReadSingle()); + bdr.Append(inc.ReadDouble()); + bdr.Append(inc.ReadVariableInt32()); + bdr.Append(inc.ReadVariableUInt32()); + + if (bdr.ToString().Equals("False-342duke of earl4344True4546-4748")) + Console.WriteLine("Read/write tests OK"); + else + throw new NetException("Read/write tests FAILED!"); + + msg = peer.CreateMessage(); + + NetOutgoingMessage tmp = peer.CreateMessage(); + tmp.Write((int)42, 14); + + msg.Write(tmp); + msg.Write(tmp); + + if (msg.LengthBits != tmp.LengthBits * 2) + throw new NetException("NetOutgoingMessage.Write(NetOutgoingMessage) failed!"); + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj new file mode 100644 index 0000000..cd8032d --- /dev/null +++ b/UnitTests/UnitTests.csproj @@ -0,0 +1,64 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {9D7AC4F7-39CD-4BC8-8F45-00B67C196340} + Exe + Properties + UnitTests + UnitTests + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + + + + + + + + {FA245447-5F23-4AA1-BD5F-8D2DDF33CFBD} + Lidgren.Network + + + + + \ No newline at end of file