using System;
using System.Collections.Generic;
using Sandbox.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
namespace AutoMiningScript
{
public partial class Program : MyGridProgram
{
public class Packet
{
public string Kind = "", From = "", Target = "", Id = "", Session = "";
public long Seq, Source, Epoch;
public bool Reliable;
public MyIni Body = new MyIni();
#if FLEET || TESTS
public bool IsPortRequest => Kind=="DOCK_REQUEST" || Kind=="DEPART_REQUEST";
public bool IsRegistration => Kind=="HELLO" || Kind=="TELEMETRY" || IsPortRequest;
#endif
public string Encode(string fleet)
{
var i = new MyIni(); i.Set("wire", "Version", 2); i.Set("wire", "Epoch", Epoch); i.Set("wire", "Fleet", fleet); i.Set("wire", "Kind", Kind); i.Set("wire", "From", From); i.Set("wire", "Target", Target); i.Set("wire", "Id", Id); i.Set("wire", "Session", Session); i.Set("wire", "Seq", Seq); i.Set("wire", "Reliable", Reliable); i.Set("wire", "Payload", Body.ToString()); return i.ToString();
}
public static bool TryDecode(string text, string fleet, out Packet packet)
{
packet = null; if (text == null || text.Length > 24000) return false;
try
{
var i = new MyIni(); if (!i.TryParse(text) || Data.ReadLong(i, "wire", "Version") != 2 || Data.Text(i,"wire", "Fleet") != fleet) return false;
var p = new Packet { Kind = Data.Text(i,"wire", "Kind"), From = Data.Text(i,"wire", "From"), Target = Data.Text(i,"wire", "Target"), Id = Data.Text(i,"wire", "Id"), Session = Data.Text(i,"wire", "Session"), Seq = Data.ReadLong(i, "wire", "Seq"), Reliable = Data.Flag(i,"wire", "Reliable") };
if (p.From.Length == 0 || p.From.Length > 64 || p.Kind.Length == 0 || p.Kind.Length > 32 || p.Id.Length > 160 || p.Session.Length == 0 || p.Session.Length > 160 || p.Seq < 1 || !p.Body.TryParse(Data.Text(i,"wire", "Payload"))) return false;
p.Epoch=Data.ReadLong(i,"wire","Epoch");if(p.Epoch<0 || p.Epoch>DateTime.MaxValue.Ticks)return false;
packet = p; return true;
}
catch (ArgumentException) { return false; }
}
}
public class Wire
{
class Pending { public Packet Packet; public long Address; public double Sent; public int Tries; }
readonly Program p;
IMyBroadcastListener broadcast;
string tag, session;
readonly Dictionary<string, Dictionary<string, long>> streams = Data.CreateDictionary<string, Dictionary<string, long>>();
readonly Dictionary<string, Pending> pending = Data.CreateDictionary<string, Pending>();
readonly Dictionary<string, long> peers = Data.CreateDictionary<string, long>();
readonly Dictionary<string, long> protectedPeers = Data.CreateDictionary<string, long>();
readonly Dictionary<string, string> peerSessions = Data.CreateDictionary<string, string>();
readonly HashSet<string> retired = new HashSet<string>();
readonly Queue<string> retiredOrder = new Queue<string>();
readonly Queue<string> forgotten = new Queue<string>();
long sequence;
int retryCursor;
public string Session => session;
public long Epoch;
public int PendingCount => pending.Count;
#if FLEET || TESTS
public Func<Packet,bool> AllowRebind;
#endif
public void ProtectPeer(string id, long address)
{ if (address != 0) protectedPeers[id] = address; else protectedPeers.Remove(id); }
public void CancelPending(string kind, string target = null)
{
var keys = new List<string>(pending.Keys);
foreach (var key in keys)
{
var packet = pending[key].Packet;
if ((kind == null || packet.Kind == kind) && (target == null || packet.Target == target)) pending.Remove(key);
}
}
public void ForgetPeer(string id)
{
CancelPending(null, id); peers.Remove(id);
// Keep a bounded replay window after releasing the address slot. New
// telemetry can register again; already processed packets cannot do so.
forgotten.Enqueue(id);
while (forgotten.Count > 128)
{
var old = forgotten.Dequeue();
if (peers.ContainsKey(old) || forgotten.Contains(old)) continue;
streams.Remove(old); peerSessions.Remove(old);
}
}
public Wire(Program owner)
{
p = owner;Reset();
}
public void Reset()
{
var channel="AMS2/"+p.Config.FleetId;
if(broadcast==null || tag!=channel)
{
if(broadcast!=null)p.IGC.DisableBroadcastListener(broadcast);
tag=channel;broadcast=p.IGC.RegisterBroadcastListener(tag);broadcast.SetMessageCallback("igc");p.IGC.UnicastListener.SetMessageCallback("igc");
}
pending.Clear();peers.Clear();protectedPeers.Clear();streams.Clear();peerSessions.Clear();retired.Clear();retiredOrder.Clear();forgotten.Clear();retryCursor=0;sequence=0;
session=p.Me.EntityId.ToString()+"-"+DateTime.UtcNow.Ticks.ToString();Epoch=0;
}
public void Send(string kind, string target, MyIni body, bool reliable = false, long address = 0)
{
var packet = new Packet { Kind = kind, From = p.Config.Id, Target = target ?? "", Session = session, Seq = ++sequence, Body = body ?? new MyIni(), Reliable = reliable };
packet.Epoch=Epoch;
packet.Id = session + "-" + packet.Seq;
if (address == 0 && packet.Target.Length > 0) peers.TryGetValue(packet.Target, out address);
if (reliable && packet.Target.Length == 0) throw Data.Invalid(L.CommonReliableTarget);
if (reliable && pending.Count >= 64) { p.Log(L.F(L.CommonReliableQueue, kind)); return; }
Transmit(packet, address);
if (reliable) pending[packet.Id] = new Pending { Packet = packet, Address = address, Sent = p.Now, Tries = 1 };
}
void Transmit(Packet packet, long address)
{
var text = packet.Encode(p.Config.FleetId);
if (text.Length > 24000) throw Data.Invalid(L.CommonMessageBudget);
if (address != 0) p.IGC.SendUnicastMessage(address, tag, text); else p.IGC.SendBroadcastMessage(tag, text, TransmissionDistance.TransmissionDistanceMax);
}
public void Tick()
{
if (!p.HasBudget(0.55)) return;
var keys = new List<string>(pending.Keys);
int sent = 0;
for (int n = 0; n < keys.Count && sent < 2 && p.HasBudget(0.6); n++)
{
retryCursor %= keys.Count; var key = keys[retryCursor++]; var item = pending[key];
if (p.Now - item.Sent < 1.5) continue;
if (item.Tries >= 6) { p.Log(L.F(L.CommonNoAck, item.Packet.Kind, item.Packet.Target)); pending.Remove(key); continue; }
if (item.Address == 0) peers.TryGetValue(item.Packet.Target, out item.Address);
Transmit(item.Packet, item.Address); item.Sent = p.Now; item.Tries++; sent++;
}
}
public void Drain(Action<Packet> receive)
{
int budget = 4, start = p.Runtime.CurrentInstructionCount;
while (budget-- > 0 && p.HasBudget(0.55) && p.Runtime.CurrentInstructionCount - start < p.Runtime.MaxInstructionCount * 0.2 && (broadcast.HasPendingMessage || p.IGC.UnicastListener.HasPendingMessage))
{
var message = p.IGC.UnicastListener.HasPendingMessage ? p.IGC.UnicastListener.AcceptMessage() : broadcast.AcceptMessage();
if (message.Tag != tag) continue;
Packet packet; if (!Packet.TryDecode(message.Data as string, p.Config.FleetId, out packet) || packet.From == p.Config.Id || (packet.Target.Length > 0 && packet.Target != p.Config.Id)) continue;
packet.Source = message.Source;
long protectedAddress;
#if FLEET || TESTS
long knownAddress;
bool addressConflict=(protectedPeers.TryGetValue(packet.From,out protectedAddress) && protectedAddress!=packet.Source) ||
(peers.TryGetValue(packet.From,out knownAddress) && knownAddress!=packet.Source);
#else
if (protectedPeers.TryGetValue(packet.From, out protectedAddress) && protectedAddress != packet.Source) continue;
#endif
if(p.Config.Role=="fleet" && packet.Epoch!=Epoch)
{
#if FLEET || TESTS
if(addressConflict)continue;
#endif
try {receive(packet);}catch(ArgumentException e){p.Log(L.F(L.CommonRejectedPacket,packet.Kind,e.Message));}
continue;
}
if(packet.From==p.Config.BaseId && packet.Epoch<Epoch)continue;
if (retired.Contains(packet.From + "/" + packet.Session)) continue;
if (packet.Kind == "ACK")
{
#if FLEET || TESTS
if(addressConflict)continue;
#endif
var ack = Data.Text(packet.Body,"ack", "Id"); Pending item; long peerAddress;
if (pending.TryGetValue(ack, out item) && item.Packet.Target == packet.From &&
(item.Address == 0 || item.Address == message.Source) &&
(!peers.TryGetValue(packet.From, out peerAddress) || peerAddress == message.Source)) pending.Remove(ack);
// ACKs confirm sends; they never register a forgotten identity or
// replace the session of a miner that is reconnecting.
continue;
}
#if FLEET || TESTS
if(addressConflict)
{
// Registration-shaped packets must pass the existing epoch and
// replay gates before fleet geometry may authorize a new address.
if(packet.Epoch!=Epoch || AllowRebind==null ||
!packet.IsRegistration ||
(peers.Count>=64 && !peers.ContainsKey(packet.From)))continue;
string currentSession;Dictionary<string,long> currentStreams;long previousSequence;
if(peerSessions.TryGetValue(packet.From,out currentSession) && currentSession==packet.Session && streams.TryGetValue(packet.From,out currentStreams) &&
(currentStreams.TryGetValue(packet.Kind,out previousSequence)?packet.Seq<=previousSequence:currentStreams.Count>=32))continue;
bool allowed=false;
try {allowed=AllowRebind(packet);}catch(ArgumentException e){p.Log(L.F(L.CommonRejectedPacket,packet.Kind,e.Message));}
if(!allowed)continue;
// Keep protection active at the approved address. The normal
// session path below retires the former session before delivery.
CancelPending(null,packet.From);peers[packet.From]=packet.Source;
if(protectedPeers.ContainsKey(packet.From))protectedPeers[packet.From]=packet.Source;
}
#endif
string forgottenSession; Dictionary<string, long> forgottenStreams; long forgottenSeq;
if (!peers.ContainsKey(packet.From) && peerSessions.TryGetValue(packet.From, out forgottenSession) && forgottenSession == packet.Session &&
streams.TryGetValue(packet.From, out forgottenStreams) && forgottenStreams.TryGetValue(packet.Kind, out forgottenSeq) && packet.Seq <= forgottenSeq)
{
if (packet.Reliable) { var ack = new MyIni(); ack.Set("ack", "Id", packet.Id); Send("ACK", packet.From, ack, false, message.Source); }
continue;
}
long known;
if (peers.TryGetValue(packet.From, out known) && known != message.Source) { p.Log(L.F(L.CommonDuplicateNode, packet.From)); continue; }
if (peers.Count >= 64 && !peers.ContainsKey(packet.From)) continue;
string oldSession;
if (peerSessions.TryGetValue(packet.From, out oldSession) && oldSession != packet.Session)
{
var retiredKey = packet.From + "/" + oldSession;
if (retired.Add(retiredKey)) retiredOrder.Enqueue(retiredKey);
while (retiredOrder.Count > 1024) retired.Remove(retiredOrder.Dequeue());
// Session replacement automatically drops obsolete sequence-cache entries.
// Retired session identities still reject late/replayed old packets.
streams.Remove(packet.From);
}
peers[packet.From] = message.Source; peerSessions[packet.From] = packet.Session;
Dictionary<string, long> peerStreams;
if (!streams.TryGetValue(packet.From, out peerStreams)) { peerStreams = Data.CreateDictionary<string, long>(); streams[packet.From] = peerStreams; }
long seen; var duplicate = peerStreams.TryGetValue(packet.Kind, out seen) && packet.Seq <= seen;
if (!duplicate && peerStreams.Count >= 32 && !peerStreams.ContainsKey(packet.Kind)) { p.Log(L.CommonStreamLimit); continue; }
if (packet.Reliable) { var ack = new MyIni(); ack.Set("ack", "Id", packet.Id); Send("ACK", packet.From, ack, false, message.Source); }
if (duplicate) continue;
peerStreams[packet.Kind] = packet.Seq;
try { receive(packet); } catch (ArgumentException e) { p.Log(L.F(L.CommonRejectedPacket, packet.Kind, e.Message)); }
}
}
}
}
}
using System;
using System.Collections.Generic;
using Sandbox.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
namespace AutoMiningScript
{
public partial class Program : MyGridProgram
{
public class Packet
{
public string Kind = "", From = "", Target = "", Id = "", Session = "";
public long Seq, Source, Epoch;
public bool Reliable;
public MyIni Body = new MyIni();
#if FLEET || TESTS
public bool IsPortRequest => Kind=="DOCK_REQUEST" || Kind=="DEPART_REQUEST";
public bool IsRegistration => Kind=="HELLO" || Kind=="TELEMETRY" || IsPortRequest;
#endif
public string Encode(string fleet)
{
var i = new MyIni(); i.Set("wire", "Version", 2); i.Set("wire", "Epoch", Epoch); i.Set("wire", "Fleet", fleet); i.Set("wire", "Kind", Kind); i.Set("wire", "From", From); i.Set("wire", "Target", Target); i.Set("wire", "Id", Id); i.Set("wire", "Session", Session); i.Set("wire", "Seq", Seq); i.Set("wire", "Reliable", Reliable); i.Set("wire", "Payload", Body.ToString()); return i.ToString();
}
public static bool TryDecode(string text, string fleet, out Packet packet)
{
packet = null; if (text == null || text.Length > 24000) return false;
try
{
var i = new MyIni(); if (!i.TryParse(text) || Data.ReadLong(i, "wire", "Version") != 2 || Data.Text(i,"wire", "Fleet") != fleet) return false;
var p = new Packet { Kind = Data.Text(i,"wire", "Kind"), From = Data.Text(i,"wire", "From"), Target = Data.Text(i,"wire", "Target"), Id = Data.Text(i,"wire", "Id"), Session = Data.Text(i,"wire", "Session"), Seq = Data.ReadLong(i, "wire", "Seq"), Reliable = Data.Flag(i,"wire", "Reliable") };
if (p.From.Length == 0 || p.From.Length > 64 || p.Kind.Length == 0 || p.Kind.Length > 32 || p.Id.Length > 160 || p.Session.Length == 0 || p.Session.Length > 160 || p.Seq < 1 || !p.Body.TryParse(Data.Text(i,"wire", "Payload"))) return false;
p.Epoch=Data.ReadLong(i,"wire","Epoch");if(p.Epoch<0 || p.Epoch>DateTime.MaxValue.Ticks)return false;
packet = p; return true;
}
catch (ArgumentException) { return false; }
}
}
public class Wire
{
class Pending { public Packet Packet; public long Address; public double Sent; public int Tries; }
readonly Program p;
IMyBroadcastListener broadcast;
string tag, session;
readonly Dictionary<string, Dictionary<string, long>> streams = Data.CreateDictionary<string, Dictionary<string, long>>();
readonly Dictionary<string, Pending> pending = Data.CreateDictionary<string, Pending>();
readonly Dictionary<string, long> peers = Data.CreateDictionary<string, long>();
readonly Dictionary<string, long> protectedPeers = Data.CreateDictionary<string, long>();
readonly Dictionary<string, string> peerSessions = Data.CreateDictionary<string, string>();
readonly HashSet<string> retired = new HashSet<string>();
readonly Queue<string> retiredOrder = new Queue<string>();
readonly Queue<string> forgotten = new Queue<string>();
long sequence;
int retryCursor;
public string Session => session;
public long Epoch;
public int PendingCount => pending.Count;
#if FLEET || TESTS
public Func<Packet,bool> AllowRebind;
#endif
public void ProtectPeer(string id, long address)
{ if (address != 0) protectedPeers[id] = address; else protectedPeers.Remove(id); }
public void CancelPending(string kind, string target = null)
{
var keys = new List<string>(pending.Keys);
foreach (var key in keys)
{
var packet = pending[key].Packet;
if ((kind == null || packet.Kind == kind) && (target == null || packet.Target == target)) pending.Remove(key);
}
}
public void ForgetPeer(string id)
{
CancelPending(null, id); peers.Remove(id);
// Keep a bounded replay window after releasing the address slot. New
// telemetry can register again; already processed packets cannot do so.
forgotten.Enqueue(id);
while (forgotten.Count > 128)
{
var old = forgotten.Dequeue();
if (peers.ContainsKey(old) || forgotten.Contains(old)) continue;
streams.Remove(old); peerSessions.Remove(old);
}
}
public Wire(Program owner)
{
p = owner;Reset();
}
public void Reset()
{
var channel="AMS2/"+p.Config.FleetId;
if(broadcast==null || tag!=channel)
{
if(broadcast!=null)p.IGC.DisableBroadcastListener(broadcast);
tag=channel;broadcast=p.IGC.RegisterBroadcastListener(tag);broadcast.SetMessageCallback("igc");p.IGC.UnicastListener.SetMessageCallback("igc");
}
pending.Clear();peers.Clear();protectedPeers.Clear();streams.Clear();peerSessions.Clear();retired.Clear();retiredOrder.Clear();forgotten.Clear();retryCursor=0;sequence=0;
session=p.Me.EntityId.ToString()+"-"+DateTime.UtcNow.Ticks.ToString();Epoch=0;
}
public void Send(string kind, string target, MyIni body, bool reliable = false, long address = 0)
{
var packet = new Packet { Kind = kind, From = p.Config.Id, Target = target ?? "", Session = session, Seq = ++sequence, Body = body ?? new MyIni(), Reliable = reliable };
packet.Epoch=Epoch;
packet.Id = session + "-" + packet.Seq;
if (address == 0 && packet.Target.Length > 0) peers.TryGetValue(packet.Target, out address);
if (reliable && packet.Target.Length == 0) throw Data.Invalid(L.CommonReliableTarget);
if (reliable && pending.Count >= 64) { p.Log(L.F(L.CommonReliableQueue, kind)); return; }
Transmit(packet, address);
if (reliable) pending[packet.Id] = new Pending { Packet = packet, Address = address, Sent = p.Now, Tries = 1 };
}
void Transmit(Packet packet, long address)
{
var text = packet.Encode(p.Config.FleetId);
if (text.Length > 24000) throw Data.Invalid(L.CommonMessageBudget);
if (address != 0) p.IGC.SendUnicastMessage(address, tag, text); else p.IGC.SendBroadcastMessage(tag, text, TransmissionDistance.TransmissionDistanceMax);
}
public void Tick()
{
if (!p.HasBudget(0.55)) return;
var keys = new List<string>(pending.Keys);
int sent = 0;
for (int n = 0; n < keys.Count && sent < 2 && p.HasBudget(0.6); n++)
{
retryCursor %= keys.Count; var key = keys[retryCursor++]; var item = pending[key];
if (p.Now - item.Sent < 1.5) continue;
if (item.Tries >= 6) { p.Log(L.F(L.CommonNoAck, item.Packet.Kind, item.Packet.Target)); pending.Remove(key); continue; }
if (item.Address == 0) peers.TryGetValue(item.Packet.Target, out item.Address);
Transmit(item.Packet, item.Address); item.Sent = p.Now; item.Tries++; sent++;
}
}
public void Drain(Action<Packet> receive)
{
int budget = 4, start = p.Runtime.CurrentInstructionCount;
while (budget-- > 0 && p.HasBudget(0.55) && p.Runtime.CurrentInstructionCount - start < p.Runtime.MaxInstructionCount * 0.2 && (broadcast.HasPendingMessage || p.IGC.UnicastListener.HasPendingMessage))
{
var message = p.IGC.UnicastListener.HasPendingMessage ? p.IGC.UnicastListener.AcceptMessage() : broadcast.AcceptMessage();
if (message.Tag != tag) continue;
Packet packet; if (!Packet.TryDecode(message.Data as string, p.Config.FleetId, out packet) || packet.From == p.Config.Id || (packet.Target.Length > 0 && packet.Target != p.Config.Id)) continue;
packet.Source = message.Source;
long protectedAddress;
#if FLEET || TESTS
long knownAddress;
bool addressConflict=(protectedPeers.TryGetValue(packet.From,out protectedAddress) && protectedAddress!=packet.Source) ||
(peers.TryGetValue(packet.From,out knownAddress) && knownAddress!=packet.Source);
#else
if (protectedPeers.TryGetValue(packet.From, out protectedAddress) && protectedAddress != packet.Source) continue;
#endif
if(p.Config.Role=="fleet" && packet.Epoch!=Epoch)
{
#if FLEET || TESTS
if(addressConflict)continue;
#endif
try {receive(packet);}catch(ArgumentException e){p.Log(L.F(L.CommonRejectedPacket,packet.Kind,e.Message));}
continue;
}
if(packet.From==p.Config.BaseId && packet.Epoch<Epoch)continue;
if (retired.Contains(packet.From + "/" + packet.Session)) continue;
if (packet.Kind == "ACK")
{
#if FLEET || TESTS
if(addressConflict)continue;
#endif
var ack = Data.Text(packet.Body,"ack", "Id"); Pending item; long peerAddress;
if (pending.TryGetValue(ack, out item) && item.Packet.Target == packet.From &&
(item.Address == 0 || item.Address == message.Source) &&
(!peers.TryGetValue(packet.From, out peerAddress) || peerAddress == message.Source)) pending.Remove(ack);
// ACKs confirm sends; they never register a forgotten identity or
// replace the session of a miner that is reconnecting.
continue;
}
#if FLEET || TESTS
if(addressConflict)
{
// Registration-shaped packets must pass the existing epoch and
// replay gates before fleet geometry may authorize a new address.
if(packet.Epoch!=Epoch || AllowRebind==null ||
!packet.IsRegistration ||
(peers.Count>=64 && !peers.ContainsKey(packet.From)))continue;
string currentSession;Dictionary<string,long> currentStreams;long previousSequence;
if(peerSessions.TryGetValue(packet.From,out currentSession) && currentSession==packet.Session && streams.TryGetValue(packet.From,out currentStreams) &&
(currentStreams.TryGetValue(packet.Kind,out previousSequence)?packet.Seq<=previousSequence:currentStreams.Count>=32))continue;
bool allowed=false;
try {allowed=AllowRebind(packet);}catch(ArgumentException e){p.Log(L.F(L.CommonRejectedPacket,packet.Kind,e.Message));}
if(!allowed)continue;
// Keep protection active at the approved address. The normal
// session path below retires the former session before delivery.
CancelPending(null,packet.From);peers[packet.From]=packet.Source;
if(protectedPeers.ContainsKey(packet.From))protectedPeers[packet.From]=packet.Source;
}
#endif
string forgottenSession; Dictionary<string, long> forgottenStreams; long forgottenSeq;
if (!peers.ContainsKey(packet.From) && peerSessions.TryGetValue(packet.From, out forgottenSession) && forgottenSession == packet.Session &&
streams.TryGetValue(packet.From, out forgottenStreams) && forgottenStreams.TryGetValue(packet.Kind, out forgottenSeq) && packet.Seq <= forgottenSeq)
{
if (packet.Reliable) { var ack = new MyIni(); ack.Set("ack", "Id", packet.Id); Send("ACK", packet.From, ack, false, message.Source); }
continue;
}
long known;
if (peers.TryGetValue(packet.From, out known) && known != message.Source) { p.Log(L.F(L.CommonDuplicateNode, packet.From)); continue; }
if (peers.Count >= 64 && !peers.ContainsKey(packet.From)) continue;
string oldSession;
if (peerSessions.TryGetValue(packet.From, out oldSession) && oldSession != packet.Session)
{
var retiredKey = packet.From + "/" + oldSession;
if (retired.Add(retiredKey)) retiredOrder.Enqueue(retiredKey);
while (retiredOrder.Count > 1024) retired.Remove(retiredOrder.Dequeue());
// Session replacement automatically drops obsolete sequence-cache entries.
// Retired session identities still reject late/replayed old packets.
streams.Remove(packet.From);
}
peers[packet.From] = message.Source; peerSessions[packet.From] = packet.Session;
Dictionary<string, long> peerStreams;
if (!streams.TryGetValue(packet.From, out peerStreams)) { peerStreams = Data.CreateDictionary<string, long>(); streams[packet.From] = peerStreams; }
long seen; var duplicate = peerStreams.TryGetValue(packet.Kind, out seen) && packet.Seq <= seen;
if (!duplicate && peerStreams.Count >= 32 && !peerStreams.ContainsKey(packet.Kind)) { p.Log(L.CommonStreamLimit); continue; }
if (packet.Reliable) { var ack = new MyIni(); ack.Set("ack", "Id", packet.Id); Send("ACK", packet.From, ack, false, message.Source); }
if (duplicate) continue;
peerStreams[packet.Kind] = packet.Seq;
try { receive(packet); } catch (ArgumentException e) { p.Log(L.F(L.CommonRejectedPacket, packet.Kind, e.Message)); }
}
}
}
}
}