class AimdCongestionController implements CongestionController {

  @VisibleForTesting
  protected UnsignedLong handleF08Rejection(
      final UnsignedLong prepareAmount, final InterledgerRejectPacket rejectPacket
  ) {
    Objects.requireNonNull(prepareAmount, "prepareAmount must not be null");
    Objects.requireNonNull(rejectPacket, "rejectPacket must not be null");

    // Compute the newMaxPacketAmount
    UnsignedLong newMaxPacketAmount;
    if (rejectPacket.getData().length > 0) {
      try {
        // Assume there's error data, because their should be.
        final AmountTooLargeErrorData amountTooLargeErrorData =
            streamCodecContext.read(AmountTooLargeErrorData.class, new ByteArrayInputStream(rejectPacket.getData()));

        final BigDecimal prepareAmountAsBigDecimal = new BigDecimal(prepareAmount.bigIntegerValue());
        final BigDecimal detailsMaxAmount = new BigDecimal(amountTooLargeErrorData.maximumAmount().bigIntegerValue());
        final BigDecimal detailsAmountReceived = new BigDecimal(
            amountTooLargeErrorData.receivedAmount().bigIntegerValue());

        if (detailsAmountReceived.equals(BigDecimal.ZERO)) {
          newMaxPacketAmount = halvePrepareAmount(prepareAmount);
        } else {
          // Prepared 10, but only sent 3, max is 2
          // receivedAmount: Local amount received by the connector
          // maxAmount: Maximum amount (inclusive, denominated in same units as the receivedAmount) the connector
          // will forward
          // Equation: new_max_packet_amount = prepare_amount * details.max_amount() / details.amount_received();
          final BigDecimal newMaxPacketAmountAsBigDecimal =
              prepareAmountAsBigDecimal.multiply(detailsMaxAmount).divide(detailsAmountReceived, RoundingMode.FLOOR);
          newMaxPacketAmount = UnsignedLong.valueOf(newMaxPacketAmountAsBigDecimal.toBigIntegerExact());
        }
      } catch (Exception e) {
        // log a warning, but otherwise eat this exception. We'll continue on using default reduction values.
        logger.warn("Unable to decode AmountTooLargeErrorData from F08 Reject packet. Setting newMaxPacketAmount to be "
            + "half the prepare amount. rejectPacket={} error={}", rejectPacket, e
        );
        newMaxPacketAmount = halvePrepareAmount(prepareAmount);
      }
    } else {
      newMaxPacketAmount = halvePrepareAmount(prepareAmount);
      logger.warn(
          "F08 Reject packet had no data payload.  Setting newMaxPacketAmount to be {} (half the prepare amount)",
          newMaxPacketAmount
      );
    }

    final UnsignedLong newMaxPacketAmountFinal = newMaxPacketAmount;
    return this.maxPacketAmount
        // If maxPacketAmount is present, take the lower of it or newMaxPacketAmount
        .map($ -> StreamUtils.min($, newMaxPacketAmountFinal))
        // Otherwise, just use newMaxPacketAmount
        .orElse(newMaxPacketAmount);
  }

  public  AimdCongestionController();
  public  AimdCongestionController(
      final UnsignedLong startAmount, final UnsignedLong increaseAmount, final BigDecimal decreaseFactor,
      final CodecContext streamCodecContext
  );

}