com.bitbreeds.webrtc.sctp.impl.SendService.java Source code

Java tutorial

Introduction

Here is the source code for com.bitbreeds.webrtc.sctp.impl.SendService.java

Source

package com.bitbreeds.webrtc.sctp.impl;

import com.bitbreeds.webrtc.common.SCTPPayloadProtocolId;
import com.bitbreeds.webrtc.common.SackUtil;
import com.bitbreeds.webrtc.common.SignalUtil;
import com.bitbreeds.webrtc.sctp.model.*;
import org.apache.commons.codec.binary.Hex;
import org.joda.time.DateTime;
import org.pcollections.HashPMap;
import org.pcollections.HashTreePMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import static com.bitbreeds.webrtc.common.SignalUtil.sign;
import static com.bitbreeds.webrtc.sctp.model.SCTPFixedAttributeType.*;

/**
 * Copyright (c) 19/05/16, Jonas Waage
 * <p>
 * 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:
 * <p>
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * <p>
 * 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.
 */

/**
 *
 * This handles all operation related to Sending payloads and receiving acknowledgements.
 *
 * This includes:
 * TSN creation
 * Storage of sent messages for resend.
 * InFlight and buffered counters
 * @see <a href="https://tools.ietf.org/html/rfc4960#section-3.3.4">SCTP congestion window</a>
 */
public class SendService {

    private static final Logger logger = LoggerFactory.getLogger(SendService.class);

    private AtomicLong sentBytes = new AtomicLong(0L);

    /**
     * Reference to handler to be able to pull parameters and when needed.
     */
    private final SCTPImpl handler;

    /**
     *
     * @param handler
     */
    public SendService(SCTPImpl handler) {
        this.handler = handler;
    }

    /**
     * Time in milliseconds before resend kicks in
     * It seems we get sacks evry second.
     * Wait 2 seconds before triggering resend.
     */
    private int resendMillis = 2000;

    /**
     * TSN for sent data
     */
    protected final AtomicInteger growingTSN = new AtomicInteger(0);

    /**
     * Mutex to access TSNS and duplicate data
     */
    private final Object dataMutex = new Object();

    /**
     * A map of sent TSNs.
     * This uses a persistent collection, which is easy to work with
     * when we need to pull it and resend.
     */
    protected HashPMap<Long, TimeData> sentTSNS = HashTreePMap.empty();

    /**
     * Set the amount of millis before a resend is attempted
     * @param resendMillis the minimum time passed before resend is attempted
     */
    public void setResendMillis(int resendMillis) {
        this.resendMillis = resendMillis;
    }

    /**
     * @return first TSN
     */
    public long getFirstTSN() {
        growingTSN.set(1);
        return growingTSN.get();
    }

    /**
     * @return increment TSN
     */
    public long getNextTSN() {
        long next = growingTSN.getAndIncrement();
        growingTSN.compareAndSet(Integer.MAX_VALUE, 1);
        return next;
    }

    /**
     * @param tsn tsn for this message
     * @param data data to send
     */
    public void addMessageToSend(long tsn, byte[] data) {
        DateTime datePlusMillis = DateTime.now().plusMillis(resendMillis);
        TimeData timeData = new TimeData(datePlusMillis, data);
        synchronized (dataMutex) {
            sentTSNS = sentTSNS.plus(tsn, timeData);
        }
    }

    /**
     * This decides what messages are to be resent.
     * It is decided based on whether the time passed since the message was
     * sent is above resendMillis.
     *
     * The time can be substantially larger then resendMillis if resending thread
     * has trouble sending.
     *
     * @return list of data that should be sent again since no ack has been received
     */
    public List<byte[]> getMessagesForResend() {
        List<Long> forResend = sentTSNS.entrySet().stream().filter(i -> DateTime.now().isAfter(i.getValue().time))
                .map(Map.Entry::getKey).collect(Collectors.toList());

        logger.debug("Got these TSNs for resend {}", forResend);

        return sentTSNS.values().stream().filter(i -> DateTime.now().isAfter(i.time)).map(i -> i.data)
                .collect(Collectors.toList());
    }

    /**
     * @param gaps retrieved gaps
     * @param duplicates TODO not sure what to do with these
     */
    public void updateAcknowledgedTSNS(long cumulativeTsnAck, List<SackUtil.GapAck> gaps, List<Long> duplicates) {

        final HashPMap<Long, TimeData> sent = sentTSNS;

        logger.debug("Before removal contains: " + sent);
        logger.debug("Updating acks with cumulative " + cumulativeTsnAck + " and gaps " + gaps);

        /**
         * Filter and remove those with TSN below cumulativeTsnAck.
         */
        final Collection<Long> ls = sent.keySet().stream().filter(j -> j <= cumulativeTsnAck)
                .collect(Collectors.toSet());

        synchronized (dataMutex) {
            sentTSNS = sentTSNS.minusAll(ls);
        }

        removeAllAcknowledged(cumulativeTsnAck, gaps);

        logger.debug("After remove it contains: " + sentTSNS);
    }

    /**
     *
     * @param cumulativeTsnAck lowest ack we are sure everything below has been acknowledged
     * @param gaps the acknowledged groups offset from cumulativeTsnAck
     *
     * <a href=https://tools.ietf.org/html/rfc4960#section-3.3.4>SACK spec</a>
     */
    protected void removeAllAcknowledged(long cumulativeTsnAck, List<SackUtil.GapAck> gaps) {
        gaps.forEach(i -> {
            Collection<Long> ls = sentTSNS.keySet().stream()
                    .filter(j -> j >= (cumulativeTsnAck + i.start) && j <= (cumulativeTsnAck + i.end))
                    .collect(Collectors.toSet());

            synchronized (dataMutex) {
                sentTSNS = sentTSNS.minusAll(ls);
            }
        });
    }

    /**
     *
     * @param data the data to send
     * @param ppid protocol id
     * @param header sctp header
     * @return payload data
     */
    public byte[] createPayloadMessage(byte[] data, SCTPPayloadProtocolId ppid, SCTPHeader header) {

        logger.debug("Creating payload");

        long myTSN = getNextTSN();

        Map<SCTPFixedAttributeType, SCTPFixedAttribute> attr = new HashMap<>();
        attr.put(TSN, new SCTPFixedAttribute(TSN, SignalUtil.longToFourBytes(myTSN)));
        attr.put(STREAM_IDENTIFIER_S, new SCTPFixedAttribute(STREAM_IDENTIFIER_S, SignalUtil.twoBytesFromInt(0)));
        attr.put(STREAM_SEQUENCE_NUMBER,
                new SCTPFixedAttribute(STREAM_SEQUENCE_NUMBER, SignalUtil.twoBytesFromInt(0)));
        attr.put(PROTOCOL_IDENTIFIER,
                new SCTPFixedAttribute(PROTOCOL_IDENTIFIER, new byte[] { 0, 0, 0, sign(ppid.getId()) }));

        int fixed = attr.values().stream().map(i -> i.getType().getLgt()).reduce(0, Integer::sum);

        int lgt = 4 + fixed + data.length;

        byte[] dataOut = SignalUtil.padToMultipleOfFour(data);

        SCTPChunk chunk = new SCTPChunk(SCTPMessageType.DATA, SCTPFlags.UNORDERED_UNFRAGMENTED, lgt, attr,
                new HashMap<>(), dataOut);

        SCTPMessage msg = new SCTPMessage(header, Collections.singletonList(chunk));

        byte[] finalOut = SCTPUtil.addChecksum(msg).toBytes();

        logger.debug("Sending payload with TSN: " + myTSN + " and data: " + Hex.encodeHexString(finalOut));

        sentBytes.getAndAdd(data.length);

        /**
         * Add to resend map
         * Should remove on correct SACK or too large or too long map.
         */
        addMessageToSend(myTSN, finalOut);

        return finalOut;
    }

    /**
     * @return the amount of sent bytes in this association.
     */
    public long getSentBytes() {
        return sentBytes.get();
    }
}