public TrackerConnectionID(Tracker tracker, bool trySubsequent, TorrentEvent torrentEvent, ManualResetEvent waitHandle) { this.tracker = tracker; this.trySubsequent = trySubsequent; this.torrentEvent = torrentEvent; this.waitHandle = waitHandle; }
async Task AnnounceToTier(TorrentEvent clientEvent, TrackerTier tier) { for (int i = 0; i < tier.Trackers.Count; i++) { int trackerIndex = (i + tier.ActiveTrackerIndex) % tier.Trackers.Count; var tracker = tier.Trackers[trackerIndex]; // We should really wait til after the announce to reset the timer. However // there is no way to prevent us from announcing multiple times concurrently // to the same tracker without resetting this timer. Our logic is completely // dependent on 'time since last announce' tier.LastAnnounce = ValueStopwatch.StartNew(); var result = await Announce(clientEvent, tracker); if (result) { tier.ActiveTrackerIndex = trackerIndex; tier.LastAnnounceSucceeded = true; return; } } // All trackers failed to respond. tier.LastAnnounceSucceeded = false; }
public async ReusableTask AnnounceAsync(TorrentEvent clientEvent, CancellationToken token) { // If the user initiates an Announce we need to go to the correct thread to process it. await ClientEngine.MainLoop; var args = RequestFactory.CreateAnnounce(clientEvent); var announces = new List <Task> (); for (int i = 0; i < Tiers.Count; i++) { var task = AnnounceTierAsync(Tiers[i], args, token); if (task.IsCompleted) { await task; } else { announces.Add(task.AsTask()); } } if (announces.Count > 0) { await Task.WhenAll(announces); } }
internal async Task Announce(TorrentEvent clientEvent) { if (CurrentTracker != null) { await Announce(CurrentTracker, clientEvent, true); } }
public TrackerConnectionID(Tracker tracker, bool trySubsequent, TorrentEvent torrentEvent, ManualResetEvent waitHandle) { this.tracker = tracker; this.trySubsequent = trySubsequent; this.torrentEvent = torrentEvent; this.waitHandle = waitHandle; }
async Task <bool> Announce(TorrentEvent clientEvent, ITracker tracker) { var trackerTier = Tiers.First(t => t.Trackers.Contains(tracker)); try { // If we have not announced to this Tracker tier yet then we should replace the ClientEvent. // But if we end up announcing to a different Tracker tier we may want to send the // original/unmodified args. AnnounceParameters actualArgs = RequestFactory.CreateAnnounce(clientEvent); if (!trackerTier.SentStartedEvent) { actualArgs = actualArgs.WithClientEvent(TorrentEvent.Started); } List <Peer> peers = await tracker.AnnounceAsync(actualArgs); trackerTier.LastAnnounceSucceeded = true; trackerTier.ActiveTrackerIndex = trackerTier.Trackers.IndexOf(tracker); trackerTier.SentStartedEvent |= actualArgs.ClientEvent == TorrentEvent.Started; trackerTier.LastAnnounce = ValueStopwatch.StartNew(); AnnounceComplete?.InvokeAsync(this, new AnnounceResponseEventArgs(tracker, true, peers.AsReadOnly())); return(true); } catch { } trackerTier.LastAnnounceSucceeded = false; AnnounceComplete?.InvokeAsync(this, new AnnounceResponseEventArgs(tracker, false)); return(false); }
async Task Announce(TorrentEvent clientEvent, ITracker referenceTracker) { // If the user initiates an Announce we need to go to the correct thread to process it. await ClientEngine.MainLoop; LastAnnounce.Restart(); LastUpdated = DateTime.UtcNow; var p = RequestFactory.CreateAnnounce(clientEvent); foreach (var tuple in GetNextTracker(referenceTracker)) { try { // If we have not announced to this Tracker tier yet then we should replace the ClientEvent. // But if we end up announcing to a different Tracker tier we may want to send the // original/unmodified args. var actualArgs = p; if (!tuple.Item1.SentStartedEvent) { actualArgs = actualArgs.WithClientEvent(TorrentEvent.Started); } var peers = await tuple.Item2.AnnounceAsync(actualArgs); LastAnnounceSucceeded = true; AnnounceComplete?.InvokeAsync(this, new AnnounceResponseEventArgs(tuple.Item2, true, peers.AsReadOnly())); return; } catch { } } LastAnnounceSucceeded = false; AnnounceComplete?.InvokeAsync(this, new AnnounceResponseEventArgs(null, false)); }
public override void FromDictionary(IDictionary dict) { _peer_id = dict["id"] as string; _peer_endpoint = new IPEndPoint(IPAddress.Parse(dict["addr"] as string), (int)dict["port"]); _event = (TorrentEvent)dict["event"]; }
/// <summary> /// 增加一个新函数用来快速访问所有tracker--part2 /// </summary> /// <param name="clientEvent"></param> /// <param name="tracker"></param> /// <returns></returns> private WaitHandle Announce(TorrentEvent clientEvent, Tracker tracker) { if (tracker == null) { return(new ManualResetEvent(true)); } return(Announce(tracker, clientEvent, true, new ManualResetEvent(false))); }
internal WaitHandle Announce(TorrentEvent clientEvent) { if (CurrentTracker == null) { return(new ManualResetEvent(true)); } return(Announce(CurrentTracker, clientEvent, true, new ManualResetEvent(false))); }
async Task Announce(ITracker tracker, TorrentEvent clientEvent) { if (ResponseDelay != TimeSpan.Zero) { await Task.Delay(ResponseDelay); } Announces.Add(Tuple.Create(tracker, clientEvent)); }
async ReusableTask AnnounceAsync(ITracker tracker, TorrentEvent clientEvent, CancellationToken token) { if (ResponseDelay != TimeSpan.Zero) { await Task.Delay(ResponseDelay); } Announces.Add(Tuple.Create(tracker, clientEvent)); }
/// <summary> /// 增加一个新函数用来快速访问所有tracker--part1 /// </summary> /// <param name="clientEvent"></param> public void NewAnnounce(TorrentEvent clientEvent) { foreach (TrackerTier trackertier in trackerTiers) { foreach (Tracker tracker in trackertier) { Announce(clientEvent, tracker); } } }
public AnnounceRequest WithClientEvent(TorrentEvent clientEvent) { var clone = this; if (clientEvent != ClientEvent) { clone = (AnnounceRequest)MemberwiseClone(); clone.ClientEvent = clientEvent; } return(clone); }
internal AnnounceParameters WithClientEvent(TorrentEvent clientEvent) { var clone = this; if (clientEvent != ClientEvent) { clone = (AnnounceParameters)MemberwiseClone(); clone.ClientEvent = clientEvent; } return(clone); }
/** * This initializes the PeerEntry to be stored in Dht * @param pars */ public PeerEntry(AnnounceParameters pars) { /* * The ClientAddress here could be ["ip"] in the args of GET request * or, if it is null, the remote endpoint IP of the request. * Don't use pars.RemoteAddress */ this._peer_endpoint = pars.ClientAddress; this._peer_id = pars.PeerId; this._event = pars.Event; }
/** * This initializes the PeerEntry to be stored in Dht * @param pars */ public PeerEntry(AnnounceParameters pars) { /* * The ClientAddress here could be ["ip"] in the args of GET request * or, if it is null, the remote endpoint IP of the request. * Don't use pars.RemoteAddress */ this._peer_endpoint = pars.ClientAddress; this._peer_id = pars.PeerId; this._event = pars.Event; }
public BEncodedValue Handle(PeerDetails d, TorrentEvent e, ITrackable trackable) { var c = new NameValueCollection(); c.Add("info_hash", trackable.InfoHash.UrlEncode()); c.Add("peer_id", d.peerId); c.Add("port", d.Port.ToString()); c.Add("uploaded", d.Uploaded.ToString()); c.Add("downloaded", d.Downloaded.ToString()); c.Add("left", d.Remaining.ToString()); c.Add("compact", "0"); return base.Handle(c, d.ClientAddress, false); }
public AnnounceParameters(long bytesDownloaded, long bytesUploaded, long bytesLeft, TorrentEvent clientEvent, InfoHash infohash, bool requireEncryption, string peerId, string ipaddress, int port) { this.bytesDownloaded = bytesDownloaded; this.bytesUploaded = bytesUploaded; this.bytesLeft = bytesLeft; this.clientEvent = clientEvent; this.infohash = infohash; this.requireEncryption = requireEncryption; this.peerId = peerId; this.ipaddress = ipaddress; this.port = port; }
public async Task<TorrentInfo> ProcessAnnounce(string infoHash, TorrentEvent @event, PeerInfo peer) { var torrent = await _torrents.Find(t => t.InfoHash == infoHash).FirstOrDefaultAsync(); if (torrent != null) { var currentPeer = torrent.Peers.SingleOrDefault(p => p.IpAddress == peer.IpAddress); switch (@event) { case TorrentEvent.Started: if (currentPeer != null) { currentPeer.State = State.Downloading; break; } torrent.Peers.Add(peer); break; case TorrentEvent.Stopped: if (currentPeer != null) { torrent.Peers.Remove(currentPeer); } break; case TorrentEvent.Completed: if (currentPeer != null) { currentPeer.State = State.Downloaded; } break; case TorrentEvent.None: if (currentPeer != null) { torrent.Peers.Add(peer); } break; } torrent.LastUpdateDate = DateTime.Now; await _torrents.ReplaceOneAsync(t => t.Id == torrent.Id, torrent); } else { torrent = new TorrentInfo(infoHash); torrent.Peers.Add(peer); torrent.LastUpdateDate = DateTime.Now; await _torrents.InsertOneAsync(torrent); } return torrent; }
public BEncodedValue Handle(PeerDetails d, TorrentEvent e, ITrackable trackable) { NameValueCollection c = new NameValueCollection(); c.Add("info_hash", trackable.InfoHash.UrlEncode()); c.Add("peer_id", d.peerId.UrlEncode()); c.Add("port", d.Port.ToString()); c.Add("uploaded", d.Uploaded.ToString()); c.Add("downloaded", d.Downloaded.ToString()); c.Add("left", d.Remaining.ToString()); c.Add("compact", "0"); return(base.Handle(c, d.ClientAddress, false)); }
public AnnounceParameters(long bytesDownloaded, long bytesUploaded, long bytesLeft, TorrentEvent clientEvent, InfoHash infohash, bool requireEncryption, string peerId, string ipaddress, int port) { BytesDownloaded = bytesDownloaded; BytesUploaded = bytesUploaded; BytesLeft = bytesLeft; ClientEvent = clientEvent; InfoHash = infohash; RequireEncryption = requireEncryption; PeerId = peerId; Ipaddress = ipaddress; Port = port; }
public AnnounceParameters(long bytesDownloaded, long bytesUploaded, long bytesLeft, TorrentEvent clientEvent, InfoHash infohash, bool requireEncryption, string peerId, string ipaddress, int port) { this.bytesDownloaded = bytesDownloaded; this.bytesUploaded = bytesUploaded; this.bytesLeft = bytesLeft; this.clientEvent = clientEvent; this.infohash = infohash; this.requireEncryption = requireEncryption; this.peerId = peerId; this.ipaddress = ipaddress; this.port = port; }
public WaitHandle Announce(Tracker tracker) { Check.Tracker(tracker); TrackerTier tier = trackerTiers.Find(delegate(TrackerTier t) { return(t.Trackers.Contains(tracker)); }); if (tier == null) { throw new ArgumentException("Tracker has not been registered with the manager", "tracker"); } TorrentEvent tevent = tier.SentStartedEvent ? TorrentEvent.None : TorrentEvent.Started; return(Announce(tracker, tevent, false, new ManualResetEvent(false))); }
public AnnounceParameters(long bytesDownloaded, long bytesUploaded, long bytesLeft, TorrentEvent clientEvent, InfoHash infohash, bool requireEncryption, string peerId, string ipaddress, int port) { BytesDownloaded = bytesDownloaded; BytesUploaded = bytesUploaded; BytesLeft = bytesLeft; ClientEvent = clientEvent; InfoHash = infohash; RequireEncryption = requireEncryption; PeerId = peerId; Ipaddress = ipaddress; Port = port; }
internal AnnounceParameters(long bytesDownloaded, long bytesUploaded, long bytesLeft, TorrentEvent clientEvent, InfoHash infoHash, bool requireEncryption, BEncodedString peerId, string ipAddress, int port, bool supportsEncryption) { BytesDownloaded = bytesDownloaded; BytesUploaded = bytesUploaded; BytesLeft = bytesLeft; ClientEvent = clientEvent; InfoHash = infoHash; RequireEncryption = requireEncryption; PeerId = peerId; IPAddress = ipAddress; Port = port; SupportsEncryption = supportsEncryption; }
public AnnounceRequest(long bytesDownloaded, long bytesUploaded, long bytesLeft, TorrentEvent clientEvent, InfoHash infoHash, bool requireEncryption, ReadOnlyMemory <byte> peerId, string?ipAddress, int port, bool supportsEncryption) { BytesDownloaded = bytesDownloaded; BytesUploaded = bytesUploaded; BytesLeft = bytesLeft; ClientEvent = clientEvent; InfoHash = infoHash; RequireEncryption = requireEncryption; PeerId = peerId; IPAddress = ipAddress; Port = port; SupportsEncryption = supportsEncryption; }
private WaitHandle Announce(Tracker tracker, TorrentEvent clientEvent, bool trySubsequent, ManualResetEvent waitHandle) { var engine = _manager.Engine; // If the engine is null, we have been unregistered if (engine == null) { waitHandle.Set(); return(waitHandle); } _updateSucceeded = true; _lastUpdated = DateTime.Now; var e = engine.Settings.AllowedEncryption; var requireEncryption = !Toolbox.HasEncryption(e, EncryptionTypes.PlainText); var supportsEncryption = Toolbox.HasEncryption(e, EncryptionTypes.RC4Full) || Toolbox.HasEncryption(e, EncryptionTypes.RC4Header); requireEncryption = requireEncryption && ClientEngine.SupportsEncryption; supportsEncryption = supportsEncryption && ClientEngine.SupportsEncryption; var reportedAddress = engine.Settings.ReportedAddress; var ip = reportedAddress == null ? null : reportedAddress.Address.ToString(); var port = reportedAddress == null ? engine.Listener.Endpoint.Port : reportedAddress.Port; // FIXME: In metadata mode we need to pretend we need to download data otherwise // tracker optimisations might result in no peers being sent back. long bytesLeft = 1000; if (_manager.HasMetadata) { bytesLeft = (long)((1 - _manager.Bitfield.PercentComplete / 100.0) * _manager.Torrent.Size); } var parameters = new AnnounceParameters(_manager.Monitor.DataBytesDownloaded, _manager.Monitor.DataBytesUploaded, bytesLeft, clientEvent, _infoHash, requireEncryption, _manager.Engine.PeerId, ip, port) { SupportsEncryption = supportsEncryption }; var id = new TrackerConnectionID(tracker, trySubsequent, clientEvent, waitHandle); tracker.Announce(parameters, id); return(waitHandle); }
private async Task Announce(ITracker tracker, TorrentEvent clientEvent, bool trySubsequent) { ClientEngine engine = manager.Engine; // If the engine is null, we have been unregistered if (engine == null) { return; } this.updateSucceeded = true; this.lastUpdated = DateTime.Now; EncryptionTypes e = engine.Settings.AllowedEncryption; bool requireEncryption = !Toolbox.HasEncryption(e, EncryptionTypes.PlainText); bool supportsEncryption = Toolbox.HasEncryption(e, EncryptionTypes.RC4Full) || Toolbox.HasEncryption(e, EncryptionTypes.RC4Header); requireEncryption = requireEncryption && ClientEngine.SupportsEncryption; supportsEncryption = supportsEncryption && ClientEngine.SupportsEncryption; IPEndPoint reportedAddress = engine.Settings.ReportedAddress; string ip = reportedAddress == null ? null : reportedAddress.Address.ToString(); int port = reportedAddress == null ? engine.Listener.Endpoint.Port : reportedAddress.Port; // FIXME: In metadata mode we need to pretend we need to download data otherwise // tracker optimisations might result in no peers being sent back. long bytesLeft = 1000; if (manager.HasMetadata) { bytesLeft = (long)((1 - this.manager.Bitfield.PercentComplete / 100.0) * this.manager.Torrent.Size); } AnnounceParameters p = new AnnounceParameters(this.manager.Monitor.DataBytesDownloaded, this.manager.Monitor.DataBytesUploaded, bytesLeft, clientEvent, this.infoHash, requireEncryption, manager.Engine.PeerId, ip, port); p.SupportsEncryption = supportsEncryption; try { var peers = await tracker.AnnounceAsync(p); await OnAnnounceComplete(tracker, peers, trySubsequent, clientEvent, true); } catch { await OnAnnounceComplete(tracker, new List <Peer>(), trySubsequent, clientEvent, false); } }
public AnnounceMessage(int transactionId, long connectionId, AnnounceParameters parameters) : base(1, transactionId) { this.connectionId = connectionId; if (parameters == null) return; downloaded = parameters.BytesDownloaded; infoHash = parameters.InfoHash; ip = 0; key = (uint) DateTime.Now.GetHashCode(); // FIXME: Don't do this! It should be constant left = parameters.BytesLeft; numWanted = 50; peerId = parameters.PeerId; port = (ushort) parameters.Port; torrentEvent = parameters.ClientEvent; uploaded = parameters.BytesUploaded; }
public override void Decode(byte[] buffer, int offset, int length) { connectionId = ReadLong(buffer, ref offset); if (Action != ReadInt(buffer, ref offset)) { ThrowInvalidActionException(); } TransactionId = ReadInt(buffer, ref offset); infoHash = new InfoHash(ReadBytes(buffer, ref offset, 20)); peerId = ReadString(buffer, ref offset, 20); downloaded = ReadLong(buffer, ref offset); left = ReadLong(buffer, ref offset); uploaded = ReadLong(buffer, ref offset); torrentEvent = (TorrentEvent)ReadInt(buffer, ref offset); ip = (uint)ReadInt(buffer, ref offset); key = (uint)ReadInt(buffer, ref offset); numWanted = ReadInt(buffer, ref offset); port = (ushort)ReadShort(buffer, ref offset); }
public async Task Announce(TorrentEvent clientEvent) { // If the user initiates an Announce we need to go to the correct thread to process it. await ClientEngine.MainLoop; var announces = new List <Task> (); for (int i = 0; i < Tiers.Count; i++) { var tier = Tiers[i]; var tracker = tier.ActiveTracker; var interval = tier.LastAnnounceSucceeded ? tracker.UpdateInterval : tracker.MinUpdateInterval; if (tier.TimeSinceLastAnnounce > interval && (clientEvent != TorrentEvent.Stopped || tier.LastAnnounceSucceeded)) { announces.Add(AnnounceToTier(clientEvent, tier)); } } await Task.WhenAll(announces); }
public AnnounceRequest CreateAnnounce(TorrentEvent clientEvent) { ClientEngine engine = Manager.Engine; bool requireEncryption = !engine.Settings.AllowedEncryption.Contains(EncryptionType.PlainText); bool supportsEncryption = EncryptionTypes.SupportsRC4(engine.Settings.AllowedEncryption); requireEncryption = requireEncryption && ClientEngine.SupportsEncryption; supportsEncryption = supportsEncryption && ClientEngine.SupportsEncryption; string ip = null; int port; if (engine.Settings.ReportedAddress != null) { ip = engine.Settings.ReportedAddress.Address.ToString(); port = engine.Settings.ReportedAddress.Port; } else if (engine.PeerListener.LocalEndPoint != null) { port = engine.PeerListener.LocalEndPoint.Port; } else { port = engine.Settings.ListenEndPoint?.Port ?? -1; } // FIXME: In metadata mode we need to pretend we need to download data otherwise // tracker optimisations might result in no peers being sent back. long bytesLeft = 1000; if (Manager.HasMetadata) { bytesLeft = (long)((1 - Manager.Bitfield.PercentComplete / 100.0) * Manager.Torrent.Size); } return(new AnnounceRequest(Manager.Monitor.DataBytesDownloaded, Manager.Monitor.DataBytesUploaded, bytesLeft, clientEvent, Manager.InfoHash, requireEncryption, Manager.Engine.PeerId.AsMemory().ToArray(), ip, port, supportsEncryption)); }
public AnnounceMessage(int transactionId, long connectionId, AnnounceParameters parameters) : base(1, transactionId) { this.connectionId = connectionId; if (parameters == null) { return; } this.downloaded = parameters.BytesDownloaded; this.infoHash = parameters.InfoHash; this.ip = 0; this.key = (uint)DateTime.Now.GetHashCode(); // FIXME: Don't do this! It should be constant this.left = parameters.BytesLeft; this.numWanted = 50; this.peerId = parameters.PeerId; this.port = (ushort)parameters.Port; this.torrentEvent = parameters.ClientEvent; this.uploaded = parameters.BytesUploaded; }
public AnnounceParameters CreateAnnounce(TorrentEvent clientEvent) { ClientEngine engine = Manager.Engine; EncryptionTypes e = engine.Settings.AllowedEncryption; bool requireEncryption = !e.HasFlag(EncryptionTypes.PlainText); bool supportsEncryption = e.HasFlag(EncryptionTypes.RC4Full) || e.HasFlag(EncryptionTypes.RC4Header); requireEncryption = requireEncryption && ClientEngine.SupportsEncryption; supportsEncryption = supportsEncryption && ClientEngine.SupportsEncryption; string ip = null; int port; if (engine.Settings.ReportedAddress != null) { ip = engine.Settings.ReportedAddress.Address.ToString(); port = engine.Settings.ReportedAddress.Port; } else { port = engine.Settings.ListenPort; } // FIXME: In metadata mode we need to pretend we need to download data otherwise // tracker optimisations might result in no peers being sent back. long bytesLeft = 1000; if (Manager.HasMetadata) { bytesLeft = (long)((1 - Manager.Bitfield.PercentComplete / 100.0) * Manager.Torrent.Size); } return(new AnnounceParameters(Manager.Monitor.DataBytesDownloaded, Manager.Monitor.DataBytesUploaded, bytesLeft, clientEvent, Manager.InfoHash, requireEncryption, Manager.Engine.PeerId, ip, port, supportsEncryption)); }
public AnnounceParameters CreateAnnounce(TorrentEvent clientEvent) { ClientEngine engine = Manager.Engine; bool requireEncryption = !engine.Settings.AllowedEncryption.Contains(EncryptionType.PlainText); bool supportsEncryption = EncryptionTypes.SupportsRC4(engine.Settings.AllowedEncryption); requireEncryption = requireEncryption && ClientEngine.SupportsEncryption; supportsEncryption = supportsEncryption && ClientEngine.SupportsEncryption; string ip = null; int port; if (engine.Settings.ReportedAddress != null) { ip = engine.Settings.ReportedAddress.Address.ToString(); port = engine.Settings.ReportedAddress.Port; } else if (engine.Listener is ISocketListener socketListener && (socketListener?.EndPoint.Port ?? 0) != 0) { port = socketListener.EndPoint.Port; }
public async Task Announce_SpecialEvent_DoNotSkipSecond( [Values(TorrentEvent.Started, TorrentEvent.Stopped, TorrentEvent.Completed)] TorrentEvent clientEvent) { await trackerManager.AnnounceAsync(clientEvent, CancellationToken.None); Assert.AreEqual(1, trackers[0][0].AnnouncedAt.Count, "#1a"); Assert.AreEqual(1, trackers[1][0].AnnouncedAt.Count, "#1b"); await trackerManager.AnnounceAsync(clientEvent, CancellationToken.None); Assert.AreEqual(2, trackers[0][0].AnnouncedAt.Count, "#2a"); Assert.AreEqual(2, trackers[1][0].AnnouncedAt.Count, "#2b"); for (int i = 1; i < trackers[0].Count; i++) { Assert.AreEqual(0, trackers[0][i].AnnouncedAt.Count, "#3." + i); } for (int i = 1; i < trackers[1].Count; i++) { Assert.AreEqual(0, trackers[1][i].AnnouncedAt.Count, "#4." + i); } }
public override void FromDictionary(IDictionary dict) { _peer_id = dict["id"] as string; _peer_endpoint = new IPEndPoint(IPAddress.Parse(dict["addr"] as string), (int)dict["port"]); _event = (TorrentEvent)dict["event"]; }
public override void Decode(byte[] buffer, int offset, int length) { connectionId = ReadLong(buffer, ref offset); if (Action != ReadInt(buffer, ref offset)) ThrowInvalidActionException(); TransactionId = ReadInt(buffer, ref offset); infoHash = new InfoHash(ReadBytes(buffer, ref offset, 20)); peerId = ReadString(buffer, ref offset, 20); downloaded = ReadLong(buffer, ref offset); left = ReadLong(buffer, ref offset); uploaded = ReadLong(buffer, ref offset); torrentEvent = (TorrentEvent) ReadInt(buffer, ref offset); ip = (uint) ReadInt(buffer, ref offset); key = (uint) ReadInt(buffer, ref offset); numWanted = ReadInt(buffer, ref offset); port = (ushort) ReadShort(buffer, ref offset); }