#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.IO; namespace OpenRA.Network { public class OrderPacket { readonly MemoryStream data; public OrderPacket(IEnumerable orders) { // Orders may refer to actors that no longer exist by the time // that the order is resolved. In order to ensure consistent // behaviour between local and remote clients, it is simplest // to always serialize / deserialize orders, instead of storing // the Order objects directly on the local client. data = new MemoryStream(); foreach (var o in orders) data.WriteArray(o.Serialize()); } public OrderPacket(MemoryStream data) { this.data = data; } public IEnumerable GetOrders(World world) { if (data.Length == 0) yield break; // Order deserialization depends on the current world state, // so must be deferred until we are ready to consume them. data.Position = 0; var reader = new BinaryReader(data); while (data.Position < data.Length) { var o = Order.Deserialize(world, reader); if (o != null) yield return o; } } public byte[] Serialize(int frame) { var ms = new MemoryStream((int)data.Length + 4); ms.WriteArray(BitConverter.GetBytes(frame)); data.Position = 0; data.CopyTo(ms); return ms.GetBuffer(); } public static OrderPacket Combine(IEnumerable packets) { var ms = new MemoryStream(); foreach (var packet in packets) { packet.data.Position = 0; packet.data.CopyTo(ms); } return new OrderPacket(ms); } } public static class OrderIO { static readonly OrderPacket NoOrders = new(Array.Empty()); public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data) { var ms = new MemoryStream(4 + Order.SyncHashOrderLength); ms.WriteArray(BitConverter.GetBytes(data.Frame)); ms.WriteByte((byte)OrderType.SyncHash); ms.WriteArray(BitConverter.GetBytes(data.SyncHash)); ms.WriteArray(BitConverter.GetBytes(data.DefeatState)); return ms.GetBuffer(); } public static byte[] SerializePingResponse(long timestamp, byte queueLength) { var ms = new MemoryStream(14); ms.WriteArray(BitConverter.GetBytes(0)); ms.WriteByte((byte)OrderType.Ping); ms.WriteArray(BitConverter.GetBytes(timestamp)); ms.WriteByte(queueLength); return ms.GetBuffer(); } public static bool TryParseDisconnect((int FromClient, byte[] Data) packet, out (int Frame, int ClientId) disconnect) { // Valid Disconnect packets are only ever generated by the server if (packet.FromClient != 0 || packet.Data.Length != Order.DisconnectOrderLength + 4 || packet.Data[4] != (byte)OrderType.Disconnect) { disconnect = (0, 0); return false; } var frame = BitConverter.ToInt32(packet.Data, 0); var clientId = BitConverter.ToInt32(packet.Data, 5); disconnect = (frame, clientId); return true; } public static bool TryParseSync(byte[] packet, out (int Frame, int SyncHash, ulong DefeatState) data) { if (packet.Length != 4 + Order.SyncHashOrderLength || packet[4] != (byte)OrderType.SyncHash) { data = (0, 0, 0); return false; } var frame = BitConverter.ToInt32(packet, 0); var syncHash = BitConverter.ToInt32(packet, 5); var defeatState = BitConverter.ToUInt64(packet, 9); data = (frame, syncHash, defeatState); return true; } public static bool TryParseTickScale((int FromClient, byte[] Data) packet, out float scale) { // Valid tick scale commands are only ever generated by the server if (packet.FromClient != 0 || packet.Data.Length != 9 || packet.Data[4] != (byte)OrderType.TickScale) { scale = 1; return false; } // Valid tick scale packets always have frame 0 var frame = BitConverter.ToInt32(packet.Data, 0); if (frame != 0) { scale = 1; return false; } scale = BitConverter.ToSingle(packet.Data, 5); return true; } public static bool TryParsePingRequest((int FromClient, byte[] Data) packet, out long timestamp) { // Valid Ping requests are only ever generated by the server if (packet.FromClient != 0 || packet.Data.Length != 13 || packet.Data[4] != (byte)OrderType.Ping) { timestamp = 0; return false; } // Valid Ping packets always have frame 0 var frame = BitConverter.ToInt32(packet.Data, 0); if (frame != 0) { timestamp = 0; return false; } timestamp = BitConverter.ToInt64(packet.Data, 5); return true; } public static bool TryParseAck((int FromClient, byte[] Data) packet, out int frame, out byte count) { // Ack packets are only accepted from the server if (packet.FromClient != 0 || packet.Data.Length != 6 || packet.Data[4] != (byte)OrderType.Ack) { frame = count = 0; return false; } frame = BitConverter.ToInt32(packet.Data, 0); count = packet.Data[5]; return true; } public static bool TryParseOrderPacket(byte[] packet, out (int Frame, OrderPacket Orders) data) { // Not a valid packet if (packet.Length < 4) { data = (0, null); return false; } // Wrong packet type if (packet.Length >= 5 && (packet[4] == (byte)OrderType.Disconnect || packet[4] == (byte)OrderType.SyncHash)) { data = (0, null); return false; } var frame = BitConverter.ToInt32(packet, 0); // PERF: Skip empty order frames, often per client each frame var orders = packet.Length > 4 ? new OrderPacket(new MemoryStream(packet, 4, packet.Length - 4)) : NoOrders; data = (frame, orders); return true; } } }