public SCTPMessage(SCTPStream s, SortedArray <DataChunk> chunks) { _stream = s; int tot = 0; if ((chunks.First.getFlags() & DataChunk.BEGINFLAG) == 0) { throw new Exception("[IllegalArgumentException] must start with 'start' chunk"); } if ((chunks.Last.getFlags() & DataChunk.ENDFLAG) == 0) { throw new Exception("[IllegalArgumentException] must end with 'end' chunk"); } _pPid = chunks.First.getPpid(); foreach (DataChunk dc in chunks) { tot += dc.getDataSize(); if (_pPid != dc.getPpid()) { // aaagh throw new Exception("[IllegalArgumentException] chunk has wrong ppid" + dc.getPpid() + " vs " + _pPid); } } _data = new byte[tot]; int offs = 0; foreach (DataChunk dc in chunks) { Array.Copy(dc.getData(), 0, _data, offs, dc.getDataSize()); offs += dc.getDataSize(); } }
public void closeStream(SCTPStream st) { Chunk[] cs = new Chunk[1]; if (canSend()) { //logger.LogDebug("due to reconfig stream " + st); cs[0] = reconfigState.makeClose(st); this.send(cs); } }
public SCTPMessage(SCTPStream s, DataChunk singleChunk) { _stream = s; int flags = singleChunk.getFlags(); if ((flags & DataChunk.SINGLEFLAG) > 0) { _data = singleChunk.getData(); _pPid = singleChunk.getPpid(); } else { throw new Exception("[IllegalArgumentException] must use a 'single' chunk"); } }
/* we can only demand they close their outbound streams */ /* we can request they start to close inbound (ie ask us to shut our outbound */ /* DCEP treats streams as bi-directional - so this is somewhat of an inpedance mis-match */ /* resulting in a temporary 'half closed' state */ /* mull this over.... */ public ReConfigChunk makeClose(SCTPStream st) { ReConfigChunk ret = null; logger.LogDebug($"SCTP closing stream {st}"); st.setClosing(true); lock (listOfStreamsToReset) { listOfStreamsToReset.Enqueue(st); } if (!timerIsRunning()) { ret = makeSSNResets(); } return(ret); }
public void deliver(SCTPStream s, SortedArray <DataChunk> a, SCTPStreamListener l) { logger.LogDebug("in deliver() for stream " + s.getLabel() + " with " + a.Count + " chunks. "); // strictly this should be looking at flags etc, and bundling the result into a message foreach (DataChunk dc in a) { if (dc.getDCEP() != null) { logger.LogDebug("in deliver() for a DCEP message " + dc.getDataAsString()); } else { logger.LogDebug("inbound data chunk is " + dc.ToString()); l.onMessage(s, dc.getDataAsString()); } } a.Clear(); }
// todo should be in a behave block // then we wouldn't be messing with stream seq numbers. private Chunk[] dcepDeal(SCTPStream s, DataChunk dc, DataChannelOpen dcep) { Chunk[] rep = null; //logger.LogDebug("dealing with a decp for stream " + dc.getDataAsString()); if (!dcep.isAck()) { //logger.LogDebug("decp is not an ack... "); SCTPStreamBehaviour behave = dcep.mkStreamBehaviour(); s.setBehave(behave); s.setLabel(dcep.getLabel()); lock (s) { int seqIn = s.getNextMessageSeqIn(); s.setNextMessageSeqIn(seqIn + 1); int seqOut = s.getNextMessageSeqOut(); s.setNextMessageSeqOut(seqOut + 1); } rep = new Chunk[1]; DataChunk ack = dc.mkAck(dcep); s.outbound(ack); ack.setTsn(_nearTSN++); // check rollover - will break at maxint. rep[0] = ack; } else { //logger.LogDebug("got a dcep ack for " + s.getLabel()); SCTPStreamBehaviour behave = dcep.mkStreamBehaviour(); s.setBehave(behave); lock (s) { int seqIn = s.getNextMessageSeqIn(); s.setNextMessageSeqIn(seqIn + 1); int seqOut = s.getNextMessageSeqOut(); s.setNextMessageSeqOut(seqOut + 1); } } return(rep); }
public void deliver(SCTPStream s, SortedArray <DataChunk> stash, SCTPStreamListener l) { //stash is the list of all DataChunks that have not yet been turned into whole messages //we assume that it is sorted by stream sequence number. List <DataChunk> delivered = new List <DataChunk>(); if (stash.Count == 0) { return; // I'm not fond of these early returns } long expectedTsn = stash.First.getTsn(); // This ignores gaps - but _hopefully_ messageNo will catch any // gaps we care about - ie gaps in the sequence for _this_ stream // we can deliver ordered messages on this stream even if earlier messages from other streams are missing // - this does assume that the tsn's of a message are contiguous -which is odd. foreach (DataChunk dc in stash) { int messageNo = s.getNextMessageSeqIn(); int flags = dc.getFlags() & DataChunk.SINGLEFLAG; // mask to the bits we want long tsn = dc.getTsn(); bool lookingForOrderedMessages = _ordered; if (lookingForOrderedMessages && (tsn != expectedTsn)) { logger.LogDebug("Hole in chunk sequence " + tsn + " expected " + expectedTsn); break; } switch (flags) { case DataChunk.SINGLEFLAG: if (_ordered && (messageNo != dc.getSSeqNo())) { logger.LogDebug("Hole (single) in message sequence " + dc.getSSeqNo() + " expected " + messageNo); break; // not the message we are looking for... } SCTPMessage single = new SCTPMessage(s, dc); if (single.deliver(l)) { delivered.Add(dc); messageNo++; s.setNextMessageSeqIn(messageNo); } break; case DataChunk.BEGINFLAG: case DataChunk.ENDFLAG: case DataChunk.CONTINUEFLAG: if (_ordered && (messageNo != dc.getSSeqNo())) { logger.LogDebug("Hole (single) in message sequence " + dc.getSSeqNo() + " expected " + messageNo); break; // not the message we are looking for... } var orderedMessage = _orderedMessageHandler.GetMessage(dc.getSSeqNo()) .Add(dc, flags); if (orderedMessage.IsReady) { orderedMessage.Finish(() => { SCTPMessage deliverable = new SCTPMessage(s, orderedMessage.ToArray()); if (deliverable.deliver(l)) { _orderedMessageHandler.RemoveMessage(orderedMessage); orderedMessage.AddToList(delivered); messageNo++; s.setNextMessageSeqIn(messageNo); } }); } break; default: throw new Exception("[IllegalStateException] Impossible value in stream logic"); } expectedTsn = tsn + 1; } stash.RemoveWhere((dc) => { return(delivered.Contains(dc)); }); }
public Chunk[] respond(SCTPStream a) { return(null); }
/* * * D) Any time a SACK arrives, the endpoint performs the following: * * ... * * ToDo : * * iii) If the SACK is missing a TSN that was previously acknowledged * via a Gap Ack Block (e.g., the data receiver reneged on the * data), then consider the corresponding DATA that might be * possibly missing: Count one miss indication towards Fast * Retransmit as described in Section 7.2.4, and if no * retransmit timer is running for the destination address to * which the DATA chunk was originally transmitted, then T3-rtx * is started for that destination address. * * iv) If the Cumulative TSN Ack matches or exceeds the Fast * Recovery exitpoint (Section 7.2.4), Fast Recovery is exited. * */ protected override Chunk[] sackDeal(SackChunk sack) { Chunk[] ret = { }; /* * i) If Cumulative TSN Ack is less than the Cumulative TSN Ack * Point, then drop the SACK. Since Cumulative TSN Ack is * monotonically increasing, a SACK whose Cumulative TSN Ack is * less than the Cumulative TSN Ack Point indicates an out-of- * order SACK. */ if (sack.getCumuTSNAck() >= this._lastCumuTSNAck) { long ackedTo = sack.getCumuTSNAck(); int totalAcked = 0; long now = TimeExtension.CurrentTimeMillis(); // interesting SACK // process acks lock (_inFlight) { List <long> removals = new List <long>(); foreach (var kvp in _inFlight) { if (kvp.Key <= ackedTo) { removals.Add(kvp.Key); } } foreach (long k in removals) { DataChunk d = _inFlight[k]; _inFlight.Remove(k); totalAcked += d.getDataSize(); /* * todo IMPLEMENTATION NOTE: RTT measurements should only be made using * a chunk with TSN r if no chunk with TSN less than or equal to r * is retransmitted since r is first sent. */ setRTO(now - d.getSentTime()); try { int sid = d.getStreamId(); SCTPStream stream = getStream(sid); if (stream != null) { stream.delivered(d); } lock (_freeBlocks) { _freeBlocks.Enqueue(d); } } catch (Exception ex) { logger.LogError("eek - can't replace free block on list!?!"); logger.LogError(ex.ToString()); } } } /* * Gap Ack Blocks: * * These fields contain the Gap Ack Blocks. They are repeated for * each Gap Ack Block up to the number of Gap Ack Blocks defined in * the Number of Gap Ack Blocks field. All DATA chunks with TSNs * greater than or equal to (Cumulative TSN Ack + Gap Ack Block * Start) and less than or equal to (Cumulative TSN Ack + Gap Ack * Block End) of each Gap Ack Block are assumed to have been received * correctly. */ foreach (SackChunk.GapBlock gb in sack.getGaps()) { long ts = gb.getStart() + ackedTo; long te = gb.getEnd() + ackedTo; lock (_inFlight) { for (long t = ts; t <= te; t++) { //logger.LogDebug("gap block says far end has seen " + t); DataChunk d; if (_inFlight.TryGetValue(t, out d)) { d.setGapAck(true); totalAcked += d.getDataSize(); } else { logger.LogDebug("Huh? gap for something not inFlight ?!? " + t); } } } } /* * ii) Set rwnd equal to the newly received a_rwnd minus the number * of bytes still outstanding after processing the Cumulative * TSN Ack and the Gap Ack Blocks. */ int totalDataInFlight = 0; lock (_inFlight) { foreach (var kvp in _inFlight) { DataChunk d = kvp.Value; long k = kvp.Key; if (!d.getGapAck()) { totalDataInFlight += d.getDataSize(); } } } _rwnd = sack.getArWin() - totalDataInFlight; //logger.LogDebug("Setting rwnd to " + _rwnd); bool advanced = (_lastCumuTSNAck < ackedTo); adjustCwind(advanced, totalDataInFlight, totalAcked); _lastCumuTSNAck = ackedTo; } else { logger.LogDebug("Dumping Sack - already seen later sack."); } return(ret); }
public ReConfigChunk addToCloseList(SCTPStream st) { return(reconfigState.makeClose(st)); }
public SCTPMessage(string data, SCTPStream s) { _data = (data.Length > 0) ? Encoding.UTF8.GetBytes(data) : new byte[1]; _stream = s; _pPid = (data.Length > 0) ? DataChunk.WEBRTCstring : DataChunk.WEBRTCstringEMPTY; }
/** * Outbound message - note that we assume no one will mess with data between * calls to fill() * * @param data * @param s */ public SCTPMessage(byte[] data, SCTPStream s) { _data = (data.Length > 0) ? data : new byte[1]; _stream = s; _pPid = (data.Length > 0) ? DataChunk.WEBRTCBINARY : DataChunk.WEBRTCBINARYEMPTY; }
/* * https://tools.ietf.org/html/rfc6525 */ public Chunk[] deal(ReConfigChunk rconf) { Chunk[] ret = new Chunk[1]; ReConfigChunk reply = null; //logger.LogDebug("Got a reconfig message to deal with"); if (haveSeen(rconf)) { // if not - is this a repeat reply = getPrevious(rconf); // then send the same reply } if (reply == null) { // not a repeat then reply = new ReConfigChunk(); // create a new thing if (rconf.hasOutgoingReset()) { OutgoingSSNResetRequestParameter oreset = rconf.getOutgoingReset(); int[] streams = oreset.getStreams(); if (streams.Length == 0) { streams = assoc.allStreams(); } if (timerIsRunning()) { markAsAcked(rconf); } // if we are behind, we are supposed to wait until we catch up. if (oreset.getLastAssignedTSN() > assoc.getCumAckPt()) { //logger.LogDebug("Last assigned > farTSN " + oreset.getLastAssignedTSN() + " v " + assoc.getCumAckPt()); foreach (int s in streams) { SCTPStream defstr = assoc.getStream(s); // AC: All this call did was set an unused local variable. Removed for now. //defstr.setDeferred(true); } ReconfigurationResponseParameter rep = new ReconfigurationResponseParameter(); rep.setSeq(oreset.getReqSeqNo()); rep.setResult(ReconfigurationResponseParameter.IN_PROGRESS); reply.addParam(rep); } else { // somehow invoke this when TSN catches up ?!?! ToDo //logger.LogDebug("we are up-to-date "); ReconfigurationResponseParameter rep = new ReconfigurationResponseParameter(); rep.setSeq(oreset.getReqSeqNo()); int result = streams.Length > 0 ? ReconfigurationResponseParameter.SUCCESS_PERFORMED : ReconfigurationResponseParameter.SUCCESS_NOTHING_TO_DO; rep.setResult((uint)result); // assume all good foreach (int s in streams) { SCTPStream cstrm = assoc.delStream(s); if (cstrm == null) { //logger.LogError("Close a non existant stream"); rep.setResult(ReconfigurationResponseParameter.ERROR_WRONG_SSN); break; // bidriectional might be a problem here... } else { cstrm.reset(); } } reply.addParam(rep); } } // ponder putting this in a second chunk ? if (rconf.hasIncomingReset()) { IncomingSSNResetRequestParameter ireset = rconf.getIncomingReset(); /*The Re-configuration * Response Sequence Number of the Outgoing SSN Reset Request * Parameter MUST be the Re-configuration Request Sequence Number * of the Incoming SSN Reset Request Parameter. */ OutgoingSSNResetRequestParameter rep = new OutgoingSSNResetRequestParameter(nextNearNo(), ireset.getReqNo(), assoc.getNearTSN()); int[] streams = ireset.getStreams(); rep.setStreams(streams); if (streams.Length == 0) { streams = assoc.allStreams(); } foreach (int s in streams) { SCTPStream st = assoc.getStream(s); if (st != null) { st.setClosing(true); } } reply.addParam(rep); // set outbound timer running here ??? //logger.LogDebug("Ireset " + ireset); } } if (reply.hasParam()) { ret[0] = reply; // todo should add sack here //logger.LogDebug("about to reply with " + reply.ToString()); } else { ret = null; } return(ret); }
public void deliver(SCTPStream s, SortedArray <DataChunk> stash, SCTPStreamListener l) { //stash is the list of all DataChunks that have not yet been turned into whole messages //we assume that it is sorted by stream sequence number. List <DataChunk> delivered = new List <DataChunk>(); SortedArray <DataChunk> message = null; if (stash.Count == 0) { return; // I'm not fond of these early returns } long expectedTsn = stash.First.getTsn(); // This ignores gaps - but _hopefully_ messageNo will catch any // gaps we care about - ie gaps in the sequence for _this_ stream // we can deliver ordered messages on this stream even if earlier messages from other streams are missing // - this does assume that the tsn's of a message are contiguous -which is odd. foreach (DataChunk dc in stash) { int messageNo = s.getNextMessageSeqIn(); int flags = dc.getFlags() & DataChunk.SINGLEFLAG; // mask to the bits we want long tsn = dc.getTsn(); bool lookingForOrderedMessages = _ordered || (message != null); // which is to say for unordered messages we can tolerate gaps _between_ messages // but not within them if (lookingForOrderedMessages && (tsn != expectedTsn)) { logger.LogDebug("Hole in chunk sequence " + tsn + " expected " + expectedTsn); break; } switch (flags) { case DataChunk.SINGLEFLAG: // singles are easy - just dispatch. if (_ordered && (messageNo != dc.getSSeqNo())) { logger.LogDebug("Hole (single) in message sequence " + dc.getSSeqNo() + " expected " + messageNo); break; // not the message we are looking for... } SCTPMessage single = new SCTPMessage(s, dc); if (single.deliver(l)) { delivered.Add(dc); messageNo++; s.setNextMessageSeqIn(messageNo); } break; case DataChunk.BEGINFLAG: if (_ordered && (messageNo != dc.getSSeqNo())) { logger.LogDebug("Hole (begin) in message sequence " + dc.getSSeqNo() + " expected " + messageNo); break; // not the message we are looking for... } message = new SortedArray <DataChunk>(); message.Add(dc); logger.LogDebug("new message no" + dc.getSSeqNo() + " starts with " + dc.getTsn()); break; case 0: // middle if (message != null) { message.Add(dc); logger.LogDebug("continued message no" + dc.getSSeqNo() + " with " + dc.getTsn()); } else { // perhaps check sno ? logger.LogDebug("Middle with no start" + dc.getSSeqNo() + " tsn " + dc.getTsn()); } break; case DataChunk.ENDFLAG: if (message != null) { message.Add(dc); logger.LogDebug("finished message no" + dc.getSSeqNo() + " with " + dc.getTsn()); SCTPMessage deliverable = new SCTPMessage(s, message); if (deliverable.deliver(l)) { message.AddToList(delivered); messageNo++; s.setNextMessageSeqIn(messageNo); } message = null; } else { logger.LogDebug("End with no start" + dc.getSSeqNo() + " tsn " + dc.getTsn()); message = null; } break; default: throw new Exception("[IllegalStateException] Impossible value in stream logic"); } expectedTsn = tsn + 1; } stash.RemoveWhere((dc) => { return(delivered.Contains(dc)); }); }
public Chunk[] respond(SCTPStream a) { logger.LogDebug("in respond() for a opened stream " + a.getLabel()); return(null); }