/**
   * Called from the {@link Receiver} upon receiving an RTT Measurement that is a reply.
   *
   * @param header of the measurement
   * @param srcAddress from the sender of the measurement
   */
  public void onRttMeasurement(
      final RttMeasurementFlyweight header, final InetSocketAddress srcAddress) {
    final long now = nanoClock.nanoTime();
    final long rttInNanos = now - header.echoTimestamp() - header.receptionDelta();

    congestionControl.onRttMeasurement(now, rttInNanos, srcAddress);
  }
  /**
   * Called from the {@link Receiver} thread to check for initiating an RTT measurement.
   *
   * @param now in nanoseconds
   * @return number of work items processed.
   */
  int initiateAnyRttMeasurements(final long now) {
    int workCount = 0;

    if (congestionControl.shouldMeasureRtt(now)) {
      channelEndpoint.sendRttMeasurement(controlAddress, sessionId, streamId, now, 0, true);
      workCount = 1;
    }

    return workCount;
  }
  /** {@inheritDoc} */
  public void close() {
    hwmPosition.close();
    rebuildPosition.close();
    for (final ReadablePosition position : subscriberPositions) {
      position.close();
    }

    congestionControl.close();
    rawLog.close();
  }
  /**
   * Called from the {@link DriverConductor}.
   *
   * @param now in nanoseconds
   * @param statusMessageTimeout for sending of Status Messages.
   */
  void trackRebuild(final long now, final long statusMessageTimeout) {
    long minSubscriberPosition = Long.MAX_VALUE;
    long maxSubscriberPosition = Long.MIN_VALUE;

    for (final ReadablePosition subscriberPosition : subscriberPositions) {
      final long position = subscriberPosition.getVolatile();
      minSubscriberPosition = Math.min(minSubscriberPosition, position);
      maxSubscriberPosition = Math.max(maxSubscriberPosition, position);
    }

    final long rebuildPosition = Math.max(this.rebuildPosition.get(), maxSubscriberPosition);
    final long hwmPosition = this.hwmPosition.getVolatile();

    final long scanOutcome =
        lossDetector.scan(
            termBuffers[indexByPosition(rebuildPosition, positionBitsToShift)],
            rebuildPosition,
            hwmPosition,
            now,
            termLengthMask,
            positionBitsToShift,
            initialTermId);

    final int rebuildTermOffset = (int) rebuildPosition & termLengthMask;
    final long newRebuildPosition =
        (rebuildPosition - rebuildTermOffset) + rebuildOffset(scanOutcome);
    this.rebuildPosition.proposeMaxOrdered(newRebuildPosition);

    final long ccOutcome =
        congestionControl.onTrackRebuild(
            now,
            minSubscriberPosition,
            nextSmPosition,
            hwmPosition,
            rebuildPosition,
            newRebuildPosition,
            lossFound(scanOutcome));

    final int window = CongestionControlUtil.receiverWindowLength(ccOutcome);
    final long threshold = CongestionControlUtil.positionThreshold(window);

    if (CongestionControlUtil.shouldForceStatusMessage(ccOutcome)
        || (now > (lastStatusMessageTimestamp + statusMessageTimeout))
        || (minSubscriberPosition > (nextSmPosition + threshold))) {
      scheduleStatusMessage(now, minSubscriberPosition, window);
      cleanBufferTo(minSubscriberPosition - (termLengthMask + 1));
    }
  }
  public PublicationImage(
      final long correlationId,
      final long imageLivenessTimeoutNs,
      final ReceiveChannelEndpoint channelEndpoint,
      final InetSocketAddress controlAddress,
      final int sessionId,
      final int streamId,
      final int initialTermId,
      final int activeTermId,
      final int initialTermOffset,
      final RawLog rawLog,
      final FeedbackDelayGenerator lossFeedbackDelayGenerator,
      final List<ReadablePosition> subscriberPositions,
      final Position hwmPosition,
      final Position rebuildPosition,
      final NanoClock nanoClock,
      final EpochClock epochClock,
      final SystemCounters systemCounters,
      final InetSocketAddress sourceAddress,
      final CongestionControl congestionControl,
      final LossReport lossReport,
      final boolean isReliable) {
    this.correlationId = correlationId;
    this.imageLivenessTimeoutNs = imageLivenessTimeoutNs;
    this.channelEndpoint = channelEndpoint;
    this.controlAddress = controlAddress;
    this.sessionId = sessionId;
    this.streamId = streamId;
    this.rawLog = rawLog;
    this.subscriberPositions = subscriberPositions.toArray(new ReadablePosition[0]);
    this.hwmPosition = hwmPosition;
    this.rebuildPosition = rebuildPosition;
    this.sourceAddress = sourceAddress;
    this.initialTermId = initialTermId;
    this.congestionControl = congestionControl;
    this.lossReport = lossReport;
    this.isReliable = isReliable;

    heartbeatsReceived = systemCounters.get(HEARTBEATS_RECEIVED);
    statusMessagesSent = systemCounters.get(STATUS_MESSAGES_SENT);
    nakMessagesSent = systemCounters.get(NAK_MESSAGES_SENT);
    flowControlUnderRuns = systemCounters.get(FLOW_CONTROL_UNDER_RUNS);
    flowControlOverRuns = systemCounters.get(FLOW_CONTROL_OVER_RUNS);
    lossGapFills = systemCounters.get(LOSS_GAP_FILLS);

    this.nanoClock = nanoClock;
    this.epochClock = epochClock;
    final long time = nanoClock.nanoTime();
    timeOfLastStatusChange = time;
    lastPacketTimestamp = time;

    termBuffers = rawLog.termBuffers();
    lossDetector = new LossDetector(lossFeedbackDelayGenerator, this);

    final int termLength = rawLog.termLength();
    termLengthMask = termLength - 1;
    positionBitsToShift = Integer.numberOfTrailingZeros(termLength);

    final long initialPosition =
        computePosition(activeTermId, initialTermOffset, positionBitsToShift, initialTermId);
    nextSmPosition = initialPosition;
    nextSmReceiverWindowLength = congestionControl.initialWindowLength();
    cleanPosition = initialPosition;

    hwmPosition.setOrdered(initialPosition);
    rebuildPosition.setOrdered(initialPosition);
  }