All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yamcs.cfdp.OngoingCfdpTransfer Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.cfdp;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.yamcs.YConfiguration;
import org.yamcs.YamcsServer;
import org.yamcs.cfdp.pdu.CfdpPacket;
import org.yamcs.cfdp.pdu.ConditionCode;
import org.yamcs.cfdp.pdu.DirectoryListingRequest;
import org.yamcs.cfdp.pdu.DirectoryListingResponse;
import org.yamcs.cfdp.pdu.MetadataPacket;
import org.yamcs.cfdp.pdu.OriginatingTransactionId;
import org.yamcs.cfdp.pdu.ProxyClosureRequest;
import org.yamcs.cfdp.pdu.ProxyPutRequest;
import org.yamcs.cfdp.pdu.ProxyPutResponse;
import org.yamcs.cfdp.pdu.ProxyTransmissionMode;
import org.yamcs.cfdp.pdu.ReservedMessageToUser;
import org.yamcs.cfdp.pdu.TLV;
import org.yamcs.events.EventProducer;
import org.yamcs.filetransfer.TransferMonitor;
import org.yamcs.logging.Log;
import org.yamcs.protobuf.TransferState;
import org.yamcs.utils.StringConverter;
import org.yamcs.yarch.Stream;

public abstract class OngoingCfdpTransfer implements CfdpFileTransfer {

    protected final CfdpTransactionId cfdpTransactionId;
    private Stream cfdpOut;
    protected TransferState state;
    protected final ScheduledThreadPoolExecutor executor;
    protected final EventProducer eventProducer;
    protected boolean acknowledged = false;
    protected final Log log;
    protected final long startTime;
    protected final long wallclockStartTime;
    protected final long creationTime;

    protected String transferType = PredefinedTransferTypes.UNKNOWN.toString();

    final TransferMonitor monitor;
    final long destinationId;

    // transaction unique identifier (coming from a database)
    final long id;

    protected ScheduledFuture inactivityFuture;

    final long inactivityTimeout;

    long maxAckSendFreqNanos;
    long lastAckSentTime;
    boolean logAckDrop = true;

    // accumulate the errors
    List errors = new ArrayList<>();

    enum FaultHandlingAction {
        // "Handler code" in protocol?
        // ‘0000’ — reserved for future expansion
        // ‘0001’ — issue Notice of Cancellation
        // ‘0010’ — issue Notice of Suspension
        // ‘0011’ — Ignore error
        // ‘0100’ — Abandon transaction
        // ‘0101’–‘1111’ — reserved
        SUSPEND, CANCEL, ABANDON;

        public static FaultHandlingAction fromString(String str) {
            for (FaultHandlingAction a : values()) {
                if (a.name().equalsIgnoreCase(str)) {
                    return a;
                }
            }
            return null;
        }

        public static List actions() {
            return Arrays.stream(FaultHandlingAction.values()).map(a -> a.name().toLowerCase())
                    .collect(Collectors.toList());
        }
    }

    final Map faultHandlerActions;

    public OngoingCfdpTransfer(String yamcsInstance, long id, long creationTime, ScheduledThreadPoolExecutor executor,
            YConfiguration config, CfdpTransactionId cfdpTransactionId, long destinationId, Stream cfdpOut,
            EventProducer eventProducer, TransferMonitor monitor,
            Map faultHandlerActions) {
        this.cfdpTransactionId = cfdpTransactionId;
        this.cfdpOut = cfdpOut;
        this.state = TransferState.RUNNING;
        this.executor = executor;
        this.eventProducer = eventProducer;
        this.startTime = YamcsServer.getTimeService(yamcsInstance).getMissionTime();
        this.wallclockStartTime = System.currentTimeMillis();
        this.log = new Log(this.getClass(), yamcsInstance);
        this.id = id;
        this.destinationId = destinationId;
        this.creationTime = creationTime;
        if (monitor == null) {
            throw new NullPointerException("the monitor cannot be null");
        }
        this.monitor = monitor;
        this.inactivityTimeout = config.getLong("inactivityTimeout", 10000);

        this.maxAckSendFreqNanos = config.getLong("maxAckSendFreq", 500) * 1_000_000;
        this.faultHandlerActions = faultHandlerActions;
    }

    /**
     * Sets the transfer type fom the metadata packet
     *
     * @param metadata
     *            Metadata packet
     */
    protected static String getTransferType(MetadataPacket metadata) {
        if (metadata == null) {
            return PredefinedTransferTypes.UNKNOWN.toString();
        }

        ArrayList transferTypeInfo = new ArrayList<>();
        if (metadata.getFileLength() > 0) {
            transferTypeInfo.add(PredefinedTransferTypes.FILE_TRANSFER.toString());
        } else if (metadata.getHeader().isLargeFile()) {
            transferTypeInfo.add(PredefinedTransferTypes.LARGE_FILE_TRANSFER.toString());
        }

        List options = metadata.getOptions();
        if (options == null || options.isEmpty()) {
            if (transferTypeInfo.isEmpty()) {
                return PredefinedTransferTypes.METADATA_ONLY_TRANSFER.toString();
            } else {
                return transferTypeInfo.get(0);
            }
        }

        boolean hasOriginatingTransactionId = false;
        int unknownOptions = 0;
        for (TLV option : options) {
            if (option instanceof ReservedMessageToUser) {
                if (option instanceof OriginatingTransactionId) {
                    hasOriginatingTransactionId = true;
                } else if (option instanceof ProxyPutRequest) {
                    ProxyPutRequest proxyPutRequest = (ProxyPutRequest) option;
                    transferTypeInfo.add(
                            PredefinedTransferTypes.DOWNLOAD_REQUEST + " (" + proxyPutRequest.getDestinationFileName()
                                    + " ⟵ " + proxyPutRequest.getSourceFileName() + ")");
                } else if (option instanceof ProxyPutResponse) {
                    ProxyPutResponse proxyPutResponse = (ProxyPutResponse) option;
                    transferTypeInfo.add(PredefinedTransferTypes.DOWNLOAD_REQUEST_RESPONSE + " ("
                            + proxyPutResponse.getConditionCode() + ", "
                            + (proxyPutResponse.isDataComplete() ? "Complete" : "Incomplete") + ", "
                            + proxyPutResponse.getFileStatus() + ")");
                } else if (option instanceof ProxyTransmissionMode || option instanceof ProxyClosureRequest) {
                    transferTypeInfo.add(option.toString());
                } else if (option instanceof DirectoryListingRequest) {
                    transferTypeInfo.add(PredefinedTransferTypes.DIRECTORY_LISTING_REQUEST + " ("
                            + ((DirectoryListingRequest) option).getDirectoryName() + ")");
                } else if (option instanceof DirectoryListingResponse) {
                    transferTypeInfo.add(PredefinedTransferTypes.DIRECTORY_LISTING_RESPONSE + " ("
                            + ((DirectoryListingResponse) option).getListingResponseCode() + ")");
                } else {
                    transferTypeInfo.add(((ReservedMessageToUser) option).getMessageType().toString());
                }
            } else {
                unknownOptions += 1;
            }
        }

        if (hasOriginatingTransactionId && transferTypeInfo.isEmpty()) {
            transferTypeInfo.add(PredefinedTransferTypes.ORIGINATING_TRANSACTION_ID_ONLY.toString());
        }

        if (unknownOptions > 0) {
            transferTypeInfo.add(unknownOptions + " " + PredefinedTransferTypes.UNKNOWN_METADATA_OPTION
                    + (unknownOptions > 1 ? "s" : ""));
        }

        return String.join(", ", transferTypeInfo);
    }

    public abstract void processPacket(CfdpPacket packet);

    protected void pushError(String err) {
        errors.add(err);
    }

    protected void sendPacket(CfdpPacket packet) {
        if (log.isDebugEnabled()) {
            log.debug("TXID{} sending PDU: {}", cfdpTransactionId, packet);
            log.trace("{}", StringConverter.arrayToHexString(packet.toByteArray(), true));
        }
        cfdpOut.emitTuple(packet.toTuple(this));
    }

    public final boolean isOngoing() {
        return state == TransferState.RUNNING || state == TransferState.PAUSED;
    }

    @Override
    public final TransferState getTransferState() {
        return state;
    }

    @Override
    public boolean cancellable() {
        return true;
    }

    @Override
    public boolean pausable() {
        return true;
    }

    protected abstract void onInactivityTimerExpiration();

    protected void cancelInactivityTimer() {
        if (inactivityFuture != null) {
            inactivityFuture.cancel(false);
        }
    }

    protected void rescheduleInactivityTimer() {
        cancelInactivityTimer();
        inactivityFuture = executor.schedule(this::onInactivityTimerExpiration, inactivityTimeout,
                TimeUnit.MILLISECONDS);
    }

    public OngoingCfdpTransfer pauseTransfer() {
        executor.submit(this::suspend);
        return this;
    }

    protected abstract void suspend();

    public OngoingCfdpTransfer resumeTransfer() {
        executor.submit(this::resume);
        return this;
    }

    protected abstract void resume();

    public OngoingCfdpTransfer cancelTransfer() {
        executor.submit(() -> {
            pushError("Cancel request received");
            cancel(ConditionCode.CANCEL_REQUEST_RECEIVED);
        });
        return this;
    }

    protected abstract void cancel(ConditionCode code);

    public OngoingCfdpTransfer abandonTransfer(String reason) {
        executor.submit(() -> failTransfer(reason));
        return this;
    }

    @Override
    public CfdpTransactionId getTransactionId() {
        return cfdpTransactionId;
    }

    @Override
    public boolean isReliable() {
        return acknowledged;
    }

    @Override
    public long getStartTime() {
        return startTime;
    }

    protected void failTransfer(String failureReason) {
        pushError(failureReason);
        changeState(TransferState.FAILED);
    }

    protected void changeState(TransferState newState) {
        this.state = newState;
        if (state != TransferState.RUNNING) {
            cancelInactivityTimer();
        }
        monitor.stateChanged(this);
    }

    @Override
    public String getFailuredReason() {
        return errors.stream().collect(Collectors.joining("; "));
    }

    @Override
    public long getId() {
        return id;
    }

    protected FaultHandlingAction getFaultHandlingAction(ConditionCode code) {
        FaultHandlingAction action = faultHandlerActions.get(code);
        if (action == null) {
            return FaultHandlingAction.CANCEL;
        } else {
            return action;
        }
    }

    /**
     * Return the entity id of the Sender
     */
    @Override
    public long getInitiatorEntityId() {
        return cfdpTransactionId.getInitiatorEntity();
    }

    /**
     * Return the entity id of the Receiver
     */
    @Override
    public long getDestinationId() {
        return destinationId;
    }

    @Override
    public String getTransferType() {
        return transferType;
    }

    protected void sendInfoEvent(String type, String msg) {
        eventProducer.sendInfo(type, "TXID[" + cfdpTransactionId + "] " + msg);
    }

    protected void sendWarnEvent(String type, String msg) {
        eventProducer.sendWarning(type, "TXID[" + cfdpTransactionId + "] " + msg);
    }

    protected String toEventMsg(MetadataPacket packet) {
        return packet.toJson();
    }

    @Override
    public long getCreationTime() {
        return creationTime;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy