/*
 * Copyright (c) 2023 Roc Streaming authors
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "roc_rtcp/reporter.h"
#include "roc_address/socket_addr_to_str.h"
#include "roc_core/log.h"
#include "roc_core/panic.h"
#include "roc_core/time.h"
#include "roc_packet/ntp.h"
#include "roc_packet/units.h"
#include "roc_rtcp/cname.h"
#include "roc_rtcp/headers.h"
#include "roc_rtcp/participant_info.h"
#include "roc_status/status_code.h"

namespace roc {
namespace rtcp {

Reporter::Reporter(const Config& config, IParticipant& participant, core::IArena& arena)
    : arena_(arena)
    , participant_(participant)
    , local_source_id_(0)
    , has_local_send_report_(false)
    , local_send_report_()
    , local_recv_reports_(arena)
    , stream_pool_("stream_pool", arena)
    , stream_map_(arena)
    , address_pool_("address_pool", arena)
    , address_map_(arena)
    , address_index_(arena)
    , need_rebuild_index_(true)
    , current_sr_(0)
    , current_rr_(0)
    , collision_detected_(false)
    , collision_reported_(false)
    , report_state_(State_Idle)
    , report_error_(status::StatusOK)
    , report_time_(0)
    , config_(config)
    , max_delay_(packet::ntp_2_nanoseconds(header::MaxDelay))
    , valid_(false) {
    memset(local_cname_, 0, sizeof(local_cname_));

    const ParticipantInfo part_info = participant_.participant_info();

    participant_report_mode_ = part_info.report_mode;
    participant_report_addr_ = part_info.report_address;

    if (part_info.cname == NULL || part_info.cname[0] == '\0'
        || strlen(part_info.cname) > sizeof(local_cname_) - 1) {
        roc_log(LogError, "rtcp reporter: cname() should return short non-empty string");
        return;
    }

    local_source_id_ = part_info.source_id;
    strcpy(local_cname_, part_info.cname);

    roc_log(LogDebug,
            "rtcp reporter: initializing:"
            " local_ssrc=%lu local_cname=%s report_mode=%s report_addr=%s timeout=%.3fms",
            (unsigned long)local_source_id_, cname_to_str(local_cname_).c_str(),
            participant_report_mode_ == Report_ToAddress ? "address" : "back",
            address::socket_addr_to_str(participant_report_addr_).c_str(),
            (double)config_.inactivity_timeout / core::Millisecond);

    valid_ = true;
}

Reporter::~Reporter() {
    roc_panic_if_msg(report_state_ != State_Idle,
                     "rtcp reporter: invalid state in destructor");
}

bool Reporter::is_valid() const {
    return valid_;
}

bool Reporter::is_sending() const {
    roc_panic_if(!is_valid());

    return participant_.has_send_stream();
}

bool Reporter::is_receiving() const {
    roc_panic_if(!is_valid());

    return participant_.num_recv_streams() != 0;
}

size_t Reporter::total_destinations() const {
    roc_panic_if(!is_valid());

    return address_map_.size();
}

size_t Reporter::total_streams() const {
    roc_panic_if(!is_valid());

    return stream_map_.size();
}

status::StatusCode Reporter::begin_processing(const address::SocketAddr& report_addr,
                                              core::nanoseconds_t report_time) {
    roc_panic_if(!is_valid());

    roc_panic_if_msg(report_state_ != State_Idle, "rtcp reporter: invalid call order");
    roc_panic_if_msg(report_time <= 0, "rtcp reporter: invalid timestamp");

    report_state_ = State_Processing;
    report_error_ = status::StatusOK;
    report_addr_ = report_addr;

    // Ensure that report timestamp changes every invocation
    // to a different value, we rely on that.
    report_time_ = report_time != report_time_ ? report_time : report_time + 1;

    const status::StatusCode status = refresh_streams_();
    if (status != status::StatusOK) {
        report_state_ = State_Idle;
        return status;
    }

    return status::StatusOK;
}

// Process SDES CNAME data generated by remote sender.
void Reporter::process_cname(const SdesChunk& chunk, const SdesItem& item) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from SDES chunk is stream sender or receiver (RTCP packet originator).
    const packet::stream_source_t stream_source_id = chunk.ssrc;

    if (item.text == NULL || item.text[0] == '\0') {
        // Ignore empty CNAME.
        return;
    }

    // Report to local sending or receiving stream from remote receiver or sender.
    core::SharedPtr<Stream> stream = find_stream_(stream_source_id, AutoCreate);
    if (!stream) {
        return;
    }

    if (strcmp(stream->cname, item.text) == 0) {
        // CNAME already up-to-date, nothing to do.
        return;
    }

    roc_log(LogTrace, "rtcp reporter: processing CNAME: ssrc=%lu cname=%s",
            (unsigned long)stream_source_id, cname_to_str(item.text).c_str());

    if (stream->cname[0] != '\0') {
        // CNAME was not empty and changed, re-create stream.
        // Note that we parse SDES before SR/RR because if stream should be re-created, it
        // should be done before parsing reports and filling stream data.
        // This parsing order is maintained by rtcp::Communicator.
        roc_log(LogDebug,
                "rtcp reporter: detected CNAME change, recreating stream:"
                " ssrc=%lu old_cname=%s new_cname=%s",
                (unsigned long)stream->source_id, cname_to_str(stream->cname).c_str(),
                cname_to_str(item.text).c_str());

        roc_log(LogTrace, "rtcp reporter: halt_recv_stream(): ssrc=%lu cname=%s",
                (unsigned long)stream->source_id, cname_to_str(stream->cname).c_str());

        participant_.halt_recv_stream(stream->source_id);

        remove_stream_(*stream);

        if (!(stream = find_stream_(stream_source_id, AutoCreate))) {
            return;
        }
    }

    // Update CNAME.
    strcpy(stream->cname, item.text);
    need_rebuild_index_ = true;

    // Detect collisions after we've updated CNAME.
    detect_collision_(stream_source_id);

    update_stream_(*stream);
}

// Process SR data generated by remote sender.
void Reporter::process_sr(const header::SenderReportPacket& sr) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from SR is stream sender (RTCP packet originator).
    const packet::stream_source_t send_source_id = sr.ssrc();

    detect_collision_(send_source_id);

    // Report to local receiving stream from remote sender.
    core::SharedPtr<Stream> stream = find_stream_(send_source_id, AutoCreate);
    if (!stream) {
        return;
    }

    roc_log(LogTrace, "rtcp reporter: processing SR: ssrc=%lu",
            (unsigned long)send_source_id);

    if (!stream->has_remote_send_report) {
        stream->has_remote_send_report = true;
        need_rebuild_index_ = true;
    }

    stream->remote_send_report.sender_cname = stream->cname;
    stream->remote_send_report.sender_source_id = stream->source_id;

    stream->remote_send_report.report_timestamp = packet::ntp_2_unix(sr.ntp_timestamp());
    stream->remote_send_report.stream_timestamp = sr.rtp_timestamp();

    stream->remote_send_report.packet_count =
        stream->remote_send_packet_count.update(0, sr.packet_count());

    stream->remote_send_report.byte_count =
        stream->remote_send_byte_count.update(0, sr.byte_count());

    // Update remote sender address.
    if (stream->remote_address != report_addr_) {
        roc_log(LogDebug,
                "rtcp reporter: updating stream address:"
                " ssrc=%lu old_addr=%s new_addr=%s",
                (unsigned long)stream->source_id,
                address::socket_addr_to_str(stream->remote_address).c_str(),
                address::socket_addr_to_str(report_addr_).c_str());

        stream->remote_address = report_addr_;
        need_rebuild_index_ = true;
    }

    // Update time of last received SR.
    stream->last_remote_sr = report_time_;
    stream->last_remote_sr_ntp = sr.ntp_timestamp();

    update_stream_(*stream);
}

// Process SR/RR reception block data generated by remote receiver
// (in case of SR, remote peer acts as both sender & receiver).
void Reporter::process_reception_block(const packet::stream_source_t ssrc,
                                       const header::ReceptionReportBlock& blk) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from SR/RR is stream receiver (RTCP packet originator).
    // SSRC from reception block is stream sender (RTCP packet recipient).
    const packet::stream_source_t send_source_id = blk.ssrc();
    const packet::stream_source_t recv_source_id = ssrc;

    detect_collision_(recv_source_id);

    if (!has_local_send_report_ || send_source_id != local_source_id_) {
        // This report is for different sender, not for us, so ignore it.
        // Typical for multicast sessions.
        return;
    }

    // Report to local sending stream from remote receiver.
    core::SharedPtr<Stream> stream = find_stream_(recv_source_id, AutoCreate);
    if (!stream) {
        return;
    }

    roc_log(
        LogTrace,
        "rtcp reporter: processing SR/RR reception block: send_ssrc=%lu recv_ssrc=%lu",
        (unsigned long)send_source_id, (unsigned long)recv_source_id);

    if (!stream->has_remote_recv_report) {
        stream->has_remote_recv_report = true;
        need_rebuild_index_ = true;
    }

    stream->remote_recv_report.receiver_cname = stream->cname;
    stream->remote_recv_report.receiver_source_id = stream->source_id;
    stream->remote_recv_report.sender_source_id = local_source_id_;

    stream->remote_recv_report.cum_loss = blk.cum_loss();
    stream->remote_recv_report.ext_last_seqnum = blk.last_seqnum();
    stream->remote_recv_report.jitter =
        packet::stream_timestamp_2_ns(blk.jitter(), local_send_report_.sample_rate);

    // Update LSR and DLSR.
    if (current_sr_ != 0) {
        // Restore full 64-bit timestamp from compacted 32-bit form
        // by taking high 16 bits from local clock.
        const packet::ntp_timestamp_t blk_last_sr =
            ntp_extend(packet::unix_2_ntp(current_sr_), blk.last_sr());

        stream->last_local_sr = packet::ntp_2_unix(blk_last_sr);
        stream->last_remote_dlsr = packet::ntp_2_nanoseconds(blk.delay_last_sr());
    }

    // Update remote receiver address.
    if (stream->remote_address != report_addr_) {
        roc_log(LogDebug,
                "rtcp reporter: updating stream address:"
                " ssrc=%lu old_addr=%s new_addr=%s",
                (unsigned long)stream->source_id,
                address::socket_addr_to_str(stream->remote_address).c_str(),
                address::socket_addr_to_str(report_addr_).c_str());

        stream->remote_address = report_addr_;
        need_rebuild_index_ = true;
    }

    update_stream_(*stream);
}

// Process XR DLRR data generated by remote sender.
void Reporter::process_dlrr_subblock(const header::XrPacket& xr,
                                     const header::XrDlrrSubblock& blk) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from XR is stream sender (RTCP packet originator).
    // SSRC from DLRR sub-block is stream receiver (RTCP packet recipient).
    const packet::stream_source_t send_source_id = xr.ssrc();
    const packet::stream_source_t recv_source_id = blk.ssrc();

    detect_collision_(send_source_id);

    if (recv_source_id != local_source_id_) {
        // This report is for different receiver, not for us, so ignore it.
        // Typical for multicast sessions.
        return;
    }

    // Report to local receiving stream from remote sender.
    core::SharedPtr<Stream> stream = find_stream_(send_source_id, NoAutoCreate);
    if (!stream || !stream->has_remote_send_report) {
        // Ignore DLRR if there was no matching SR.
        // DLRR without SR is not very useful.
        return;
    }

    roc_log(LogTrace,
            "rtcp reporter: processing DLRR subblock: send_ssrc=%lu recv_ssrc=%lu",
            (unsigned long)send_source_id, (unsigned long)recv_source_id);

    // Update LRR and DLRR.
    if (current_rr_ != 0) {
        // Restore full 64-bit timestamp from compacted 32-bit form
        // by taking high 16 bits from local clock.
        const packet::ntp_timestamp_t blk_last_rr =
            ntp_extend(packet::unix_2_ntp(current_rr_), blk.last_rr());

        stream->last_local_rr = packet::ntp_2_unix(blk_last_rr);
        stream->last_remote_dlrr = packet::ntp_2_nanoseconds(blk.delay_last_rr());
    }

    // Update estimated RTT.
    // Do it only if we sent RR and received SR and DLRR.
    if (stream->last_local_rr != 0 && stream->last_remote_sr != 0) {
        stream->remote_send_rtt.update(
            /* when we sent RR */ stream->last_local_rr,
            /* when remote received RR */ stream->remote_send_report.report_timestamp
                - stream->last_remote_dlrr,
            /* when remote sent SR */ stream->remote_send_report.report_timestamp,
            /* when we received SR */ stream->last_remote_sr);

        if (stream->remote_send_rtt.has_metrics()) {
            const RttMetrics& metrics = stream->remote_send_rtt.metrics();

            stream->remote_send_report.clock_offset = metrics.clock_offset;
            stream->remote_send_report.rtt = metrics.rtt;
        }
    }

    update_stream_(*stream);
}

// Process XR RRTR data generated by remote receiver.
void Reporter::process_rrtr_block(const header::XrPacket& xr,
                                  const header::XrRrtrBlock& blk) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from XR is stream receiver (RTCP packet originator).
    const packet::stream_source_t recv_source_id = xr.ssrc();

    detect_collision_(recv_source_id);

    // Report to local sending stream from remote receiver.
    core::SharedPtr<Stream> stream = find_stream_(recv_source_id, NoAutoCreate);
    if (!stream || !stream->has_remote_recv_report) {
        // Ignore RRTR if there was no matching SR/RR reception block.
        // RRTR does not contain sender SSRC, so to decide whether this report is
        // for us, we update only streams already matched by reception blocks.
        return;
    }

    roc_log(LogTrace, "rtcp reporter: processing RRTR block: recv_ssrc=%lu",
            (unsigned long)recv_source_id);

    stream->remote_recv_report.report_timestamp = packet::ntp_2_unix(blk.ntp_timestamp());

    // Update time of last received RR/RRTR.
    stream->last_remote_rr = report_time_;
    stream->last_remote_rr_ntp = blk.ntp_timestamp();

    // Update estimated RTT.
    // Do it only if we sent SR and received RR and DLSR.
    if (stream->last_local_sr != 0 && stream->last_remote_rr != 0) {
        stream->remote_recv_rtt.update(
            /* when we sent SR */ stream->last_local_sr,
            /* when remote received SR */ stream->remote_recv_report.report_timestamp
                - stream->last_remote_dlsr,
            /* when remote sent RR */ stream->remote_recv_report.report_timestamp,
            /* when we received RR */ stream->last_remote_rr);

        if (stream->remote_recv_rtt.has_metrics()) {
            const RttMetrics& metrics = stream->remote_recv_rtt.metrics();

            stream->remote_recv_report.clock_offset = metrics.clock_offset;
            stream->remote_recv_report.rtt = metrics.rtt;
        }
    }

    update_stream_(*stream);
}

// Process XR Measurement Info data generated by remote receiver.
void Reporter::process_measurement_info_block(const header::XrPacket& xr,
                                              const header::XrMeasurementInfoBlock& blk) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from XR is stream receiver (RTCP packet originator).
    // SSRC from Delay Metrics block is stream sender (RTCP packet recipient).
    const packet::stream_source_t recv_source_id = xr.ssrc();
    const packet::stream_source_t send_source_id = blk.ssrc();

    detect_collision_(recv_source_id);

    if (send_source_id != local_source_id_) {
        // This report is for different sender, not for us, so ignore it.
        // Typical for multicast sessions.
        return;
    }

    // Report to local sending stream from remote receiver.
    core::SharedPtr<Stream> stream = find_stream_(recv_source_id, NoAutoCreate);
    if (!stream || !stream->has_remote_recv_report) {
        // Ignore Delay Metrics if there was no matching SR.
        return;
    }

    roc_log(
        LogTrace,
        "rtcp reporter: processing Measurement Info block: send_ssrc=%lu recv_ssrc=%lu",
        (unsigned long)send_source_id, (unsigned long)recv_source_id);

    stream->remote_recv_report.ext_first_seqnum = blk.first_seq();

    stream->remote_recv_report.packet_count = stream->remote_recv_packet_count.update(
        stream->remote_recv_report.ext_first_seqnum,
        stream->remote_recv_report.ext_last_seqnum + 1);

    update_stream_(*stream);
}

// Process XR Delay Metrics data generated by remote receiver.
void Reporter::process_delay_metrics_block(const header::XrPacket& xr,
                                           const header::XrDelayMetricsBlock& blk) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from XR is stream receiver (RTCP packet originator).
    // SSRC from Delay Metrics block is stream sender (RTCP packet recipient).
    const packet::stream_source_t recv_source_id = xr.ssrc();
    const packet::stream_source_t send_source_id = blk.ssrc();

    detect_collision_(recv_source_id);

    if (send_source_id != local_source_id_) {
        // This report is for different sender, not for us, so ignore it.
        // Typical for multicast sessions.
        return;
    }

    // Report to local sending stream from remote receiver.
    core::SharedPtr<Stream> stream = find_stream_(recv_source_id, NoAutoCreate);
    if (!stream || !stream->has_remote_recv_report) {
        // Ignore Delay Metrics if there was no matching SR.
        return;
    }

    roc_log(LogTrace,
            "rtcp reporter: processing Delay Metrics block: send_ssrc=%lu recv_ssrc=%lu",
            (unsigned long)send_source_id, (unsigned long)recv_source_id);

    if (blk.has_e2e_latency()) {
        stream->remote_recv_report.e2e_latency =
            packet::ntp_2_nanoseconds(blk.e2e_latency());
    }

    update_stream_(*stream);
}

// Process XR Queue Metrics data generated by remote receiver.
void Reporter::process_queue_metrics_block(const header::XrPacket& xr,
                                           const header::XrQueueMetricsBlock& blk) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from XR is stream receiver (RTCP packet originator).
    // SSRC from Queue Metrics block is stream sender (RTCP packet recipient).
    const packet::stream_source_t recv_source_id = xr.ssrc();
    const packet::stream_source_t send_source_id = blk.ssrc();

    detect_collision_(recv_source_id);

    if (send_source_id != local_source_id_) {
        // This report is for different sender, not for us, so ignore it.
        // Typical for multicast sessions.
        return;
    }

    // Report to local sending stream from remote receiver.
    core::SharedPtr<Stream> stream = find_stream_(recv_source_id, NoAutoCreate);
    if (!stream || !stream->has_remote_recv_report) {
        // Ignore Queue Metrics if there was no matching SR.
        return;
    }

    roc_log(LogTrace,
            "rtcp reporter: processing Queue Metrics block: send_ssrc=%lu recv_ssrc=%lu",
            (unsigned long)send_source_id, (unsigned long)recv_source_id);

    if (blk.has_niq_latency()) {
        stream->remote_recv_report.niq_latency =
            packet::ntp_2_nanoseconds(blk.niq_latency());
    }

    if (blk.has_niq_stalling()) {
        stream->remote_recv_report.niq_stalling =
            packet::ntp_2_nanoseconds(blk.niq_stalling());
    }

    update_stream_(*stream);
}

// Process BYE message generated by sender.
void Reporter::process_goodbye(const packet::stream_source_t ssrc) {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // SSRC from BYE is stream sender (RTCP packet originator).
    const packet::stream_source_t send_source_id = ssrc;

    // Terminate local receiving stream after receiving BYE message from remote sender.
    core::SharedPtr<Stream> stream = find_stream_(send_source_id, NoAutoCreate);

    roc_log(LogDebug,
            "rtcp reporter: received BYE message, terminating stream:"
            " ssrc=%lu cname=%s",
            (unsigned long)ssrc, cname_to_str(stream ? stream->cname : NULL).c_str());

    if (stream) {
        remove_stream_(*stream);
    }

    roc_log(LogTrace, "rtcp reporter: halt_recv_stream(): send_ssrc=%lu send_cname=%s",
            (unsigned long)send_source_id,
            cname_to_str(stream ? stream->cname : NULL).c_str());

    participant_.halt_recv_stream(send_source_id);
}

status::StatusCode Reporter::end_processing() {
    roc_panic_if_msg(report_state_ != State_Processing,
                     "rtcp reporter: invalid call order");

    // Detect and remove timed-out streams.
    detect_timeouts_();

    // Notify IParticipant with updated reports.
    const status::StatusCode code = notify_streams_();

    report_state_ = State_Idle;

    if (code != status::StatusOK) {
        return code;
    }

    return report_error_;
}

status::StatusCode Reporter::begin_generation(core::nanoseconds_t report_time) {
    roc_panic_if(!is_valid());

    roc_panic_if_msg(report_state_ != State_Idle, "rtcp reporter: invalid call order");
    roc_panic_if_msg(report_time <= 0, "rtcp reporter: invalid timestamp");

    report_state_ = State_Generating;
    report_error_ = status::StatusOK;

    // Ensure that report timestamp changes every invocation
    // to a different value, we rely on that.
    report_time_ = report_time != report_time_ ? report_time : report_time + 1;

    const status::StatusCode status = refresh_streams_();
    if (status != status::StatusOK) {
        report_state_ = State_Idle;
        return status;
    }

    return status::StatusOK;
}

// Get number of destination addresses to which to send reports.
size_t Reporter::num_dest_addresses() const {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    return address_index_.size();
}

// Get number of blocks to be reported if we're sending.
size_t Reporter::num_sending_streams(size_t addr_index) const {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    return address_index_[addr_index]->send_stream_index.size();
}

// Get number of blocks to be reported if we're receiving.
size_t Reporter::num_receiving_streams(size_t addr_index) const {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    return address_index_[addr_index]->recv_stream_index.size();
}

// Generate destination address to which report should be sent.
void Reporter::generate_dest_address(size_t addr_index, address::SocketAddr& addr) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    addr = address_index_[addr_index]->remote_address;
}

// Generate SDES CNAME to deliver to remote receiver.
void Reporter::generate_cname(SdesChunk& chunk, SdesItem& item) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    chunk.ssrc = local_source_id_;

    item.type = header::SDES_CNAME;
    item.text = local_cname_;
}

// Generate SR to deliver to remote receiver.
void Reporter::generate_sr(header::SenderReportPacket& sr) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    roc_panic_if_msg(!is_sending(), "rtcp reporter: SR can be generated only by sender");

    sr.reset();

    sr.set_ssrc(local_source_id_);

    sr.set_ntp_timestamp(packet::unix_2_ntp(report_time_));
    sr.set_rtp_timestamp(local_send_report_.stream_timestamp);

    sr.set_packet_count((uint32_t)local_send_report_.packet_count);
    sr.set_byte_count((uint32_t)local_send_report_.byte_count);

    // Update time of most recent SR.
    current_sr_ = report_time_;
}

// Generate RR to deliver to remote sender.
void Reporter::generate_rr(header::ReceiverReportPacket& rr) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    rr.reset();

    rr.set_ssrc(local_source_id_);

    // Update time of most recent RR.
    current_rr_ = report_time_;
}

// Generate SR/RR reception block to deliver to remote sender.
void Reporter::generate_reception_block(size_t addr_index,
                                        size_t stream_index,
                                        header::ReceptionReportBlock& blk) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    roc_panic_if_msg(
        !is_receiving(),
        "rtcp reporter: SR/RR reception block can be generated only by receiver");

    Stream* stream = address_index_[addr_index]->recv_stream_index[stream_index];
    roc_panic_if(!stream);

    blk.reset();

    blk.set_ssrc(stream->source_id);

    blk.set_last_seqnum(stream->local_recv_report->ext_last_seqnum);
    blk.set_cum_loss(stream->local_recv_report->cum_loss);
    blk.set_fract_loss(stream->local_recv_loss.update(
        stream->local_recv_report->packet_count, stream->local_recv_report->cum_loss));

    if (stream->local_recv_report->jitter > 0) {
        blk.set_jitter(packet::ns_2_stream_timestamp(
            stream->local_recv_report->jitter, stream->local_recv_report->sample_rate));
    }

    if (stream->last_remote_sr > 0 && stream->last_remote_sr <= report_time_) {
        // LSR: ntp timestamp from last SR in remote clock domain.
        blk.set_last_sr(stream->last_remote_sr_ntp);
        // DLSR: delay since last SR in local clock domain.
        blk.set_delay_last_sr(
            packet::nanoseconds_2_ntp(report_time_ - stream->last_remote_sr));
    }
}

// Generate XR header to deliver to remote receiver or sender.
void Reporter::generate_xr(header::XrPacket& xr) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    roc_panic_if_msg(!is_sending() && !is_receiving(),
                     "rtcp reporter: XR can be generated only by sender or receiver");

    xr.reset();

    xr.set_ssrc(local_source_id_);
}

// Generate XR DLRR sub-block to deliver to remote receiver.
void Reporter::generate_dlrr_subblock(size_t addr_index,
                                      size_t stream_index,
                                      header::XrDlrrSubblock& blk) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    roc_panic_if_msg(!is_sending(),
                     "rtcp reporter: DLRR can be generated only by sender");

    Stream* stream = address_index_[addr_index]->send_stream_index[stream_index];
    roc_panic_if(!stream);

    blk.reset();

    blk.set_ssrc(stream->source_id);

    if (stream->last_remote_rr > 0 && stream->last_remote_rr <= report_time_) {
        // LRR: ntp timestamp from last RR in remote clock domain.
        blk.set_last_rr(stream->last_remote_rr_ntp);
        // DLRR: delay since last RR in local clock domain.
        blk.set_delay_last_rr(
            packet::nanoseconds_2_ntp(report_time_ - stream->last_remote_rr));
    }
}

// Generate XR RRTR header to deliver to remote sender.
void Reporter::generate_rrtr_block(header::XrRrtrBlock& blk) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    roc_panic_if_msg(!is_receiving(),
                     "rtcp reporter: RRTR can be generated only by receiver");

    blk.reset();

    blk.set_ntp_timestamp(packet::unix_2_ntp(report_time_));
}

// Generate XR Measurement Info block to deliver to remote sender.
void Reporter::generate_measurement_info_block(size_t addr_index,
                                               size_t stream_index,
                                               header::XrMeasurementInfoBlock& blk) {
    roc_panic_if_msg(!is_receiving(),
                     "rtcp reporter: Measurement Info can be generated only by receiver");

    Stream* stream = address_index_[addr_index]->recv_stream_index[stream_index];
    roc_panic_if(!stream);

    blk.reset();

    // We only report sampled values, so we set cumulative and interval
    // durations to zero.
    blk.set_ssrc(stream->source_id);
    blk.set_first_seq((packet::seqnum_t)stream->local_recv_report->ext_first_seqnum);
    blk.set_interval_first_seq(stream->local_recv_report->ext_last_seqnum);
    blk.set_interval_last_seq(stream->local_recv_report->ext_last_seqnum);
    blk.set_interval_duration(0);
    blk.set_cum_duration(0);
}

// Generate XR Delay Metrics block to deliver to remote sender.
void Reporter::generate_delay_metrics_block(size_t addr_index,
                                            size_t stream_index,
                                            header::XrDelayMetricsBlock& blk) {
    roc_panic_if_msg(!is_receiving(),
                     "rtcp reporter: Delay Metrics can be generated only by receiver");

    Stream* stream = address_index_[addr_index]->recv_stream_index[stream_index];
    roc_panic_if(!stream);

    blk.reset();

    blk.set_ssrc(stream->source_id);
    blk.set_metric_flag(header::MetricFlag_SampledValue);

    if (stream->remote_send_rtt.has_metrics()) {
        const RttMetrics& metrics = stream->remote_send_rtt.metrics();

        blk.set_mean_rtt(packet::nanoseconds_2_ntp(metrics.rtt));
        blk.set_min_rtt(packet::nanoseconds_2_ntp(metrics.rtt));
        blk.set_max_rtt(packet::nanoseconds_2_ntp(metrics.rtt));
    }

    if (stream->local_recv_report->e2e_latency > 0) {
        blk.set_e2e_latency(
            packet::nanoseconds_2_ntp(stream->local_recv_report->e2e_latency));
    }
}

// Generate XR Queue Metrics block to deliver to remote sender.
void Reporter::generate_queue_metrics_block(size_t addr_index,
                                            size_t stream_index,
                                            header::XrQueueMetricsBlock& blk) {
    roc_panic_if_msg(!is_receiving(),
                     "rtcp reporter: Queue Metrics can be generated only by receiver");

    Stream* stream = address_index_[addr_index]->recv_stream_index[stream_index];
    roc_panic_if(!stream);

    blk.reset();

    blk.set_ssrc(stream->source_id);
    blk.set_metric_flag(header::MetricFlag_SampledValue);

    if (stream->local_recv_report->niq_latency > 0) {
        blk.set_niq_latency(
            packet::nanoseconds_2_ntp(stream->local_recv_report->niq_latency));
        blk.set_niq_stalling(
            packet::nanoseconds_2_ntp(stream->local_recv_report->niq_stalling));
    }
}

bool Reporter::need_goodbye() const {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    return collision_detected_;
}

void Reporter::generate_goodbye(packet::stream_source_t& ssrc) {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    ssrc = local_source_id_;

    if (collision_detected_) {
        collision_reported_ = true;
    }
}

status::StatusCode Reporter::end_generation() {
    roc_panic_if_msg(report_state_ != State_Generating,
                     "rtcp reporter: invalid call order");

    report_state_ = State_Idle;

    // Resolve SSRC collision (by choosing another SSRC).
    // We do it after generation is done, when we already sent BYE packet
    // for old SSRC.
    resolve_collision_();

    // Detect and remove timed-out streams.
    detect_timeouts_();

    return report_error_;
}

status::StatusCode Reporter::notify_streams_() {
    if (report_time_ == 0) {
        return status::StatusOK;
    }

    const bool is_sending = participant_.has_send_stream();

    for (Stream* stream = stream_lru_.front(); stream != NULL;
         stream = stream_lru_.nextof(*stream)) {
        // Recently updated streams are moved to the front of the list.
        // We iterate from front to back and stop when we find first stream which
        // was not updated during processing of current report.
        if (stream->last_update != report_time_) {
            break;
        }

        // Skip looped back streams.
        if (stream->is_looped) {
            continue;
        }

        if (stream->has_remote_recv_report && is_sending) {
            // If we're sending, notify pipeline about every discovered
            // receiver report for our sending stream.
            const packet::stream_source_t recv_source_id = stream->source_id;

            roc_log(LogTrace,
                    "rtcp reporter:"
                    " notify_send_stream(): send_ssrc=%lu recv_ssrc=%lu",
                    (unsigned long)local_source_id_, (unsigned long)recv_source_id);

            const status::StatusCode code = participant_.notify_send_stream(
                recv_source_id, stream->remote_recv_report);

            if (code != status::StatusOK) {
                roc_log(LogError,
                        "rtcp reporter:"
                        " failed to notify send stream: send_ssrc=%lu recv_ssrc=%lu",
                        (unsigned long)local_source_id_, (unsigned long)recv_source_id);
                return code;
            }
        }

        if (stream->has_remote_send_report) {
            // Notify pipeline about every discovered sender report, even if
            // we're not receiving its stream yet. Pipeline may use info from
            // this report later when it starts actually receiving the stream.
            const packet::stream_source_t send_source_id = stream->source_id;

            roc_log(LogTrace,
                    "rtcp reporter:"
                    " notify_recv_stream(): recv_ssrc=%lu send_ssrc=%lu send_cname=%s",
                    (unsigned long)local_source_id_, (unsigned long)send_source_id,
                    cname_to_str(stream->cname).c_str());

            const status::StatusCode code = participant_.notify_recv_stream(
                send_source_id, stream->remote_send_report);

            if (code != status::StatusOK) {
                roc_log(LogError,
                        "rtcp reporter:"
                        " failed to notify recv stream: recv_ssrc=%lu send_ssrc=%lu",
                        (unsigned long)local_source_id_, (unsigned long)send_source_id);
                return code;
            }
        }
    }

    return status::StatusOK;
}

status::StatusCode Reporter::refresh_streams_() {
    status::StatusCode status;

    // Query up-to-date reports from IParticipant.
    if ((status = query_streams_()) != status::StatusOK) {
        return status;
    }

    // Rebuild index if needed.
    // This happens if local or remote streams appeared or disappeared.
    // Most times it doesn't happen and it's enough to update existing
    // streams via query_streams_() above.
    if (need_rebuild_index_) {
        if ((status = rebuild_index_()) != status::StatusOK) {
            return status;
        }
        need_rebuild_index_ = false;
    }

    return status::StatusOK;
}

status::StatusCode Reporter::query_streams_() {
    // Query report of local sending stream.
    const bool is_sending = participant_.has_send_stream();

    if (is_sending) {
        roc_log(LogTrace,
                "rtcp reporter: query_send_stream(): send_ssrc=%lu send_cname=%s",
                (unsigned long)local_send_report_.sender_source_id,
                cname_to_str(local_send_report_.sender_cname).c_str());

        local_send_report_ = participant_.query_send_stream(report_time_);
        has_local_send_report_ = true;

        validate_send_report_(local_send_report_);
    } else {
        has_local_send_report_ = false;
    }

    // Query reports of local receiving streams.
    const size_t recv_count = participant_.num_recv_streams();

    if (local_recv_reports_.size() != recv_count) {
        if (!local_recv_reports_.resize(recv_count)) {
            return status::StatusNoMem;
        }

        // Receiving stream list changed, need to rebuild index
        // and update pointers to reports.
        need_rebuild_index_ = true;
    }

    if (recv_count != 0) {
        roc_log(LogTrace, "rtcp reporter: query_recv_streams(): n_reports=%lu",
                (unsigned long)recv_count);

        participant_.query_recv_streams(local_recv_reports_.data(),
                                        local_recv_reports_.size(), report_time_);

        for (size_t recv_idx = 0; recv_idx < recv_count; recv_idx++) {
            validate_recv_report_(local_recv_reports_[recv_idx]);

            if (!need_rebuild_index_) {
                core::SharedPtr<Stream> stream = find_stream_(
                    local_recv_reports_[recv_idx].sender_source_id, NoAutoCreate);

                if (!stream
                    || stream->source_id
                        != local_recv_reports_[recv_idx].sender_source_id) {
                    // Receiving stream list changed, need to rebuild index
                    // and update pointers to reports.
                    need_rebuild_index_ = true;
                }
            }
        }
    }

    return status::StatusOK;
}

status::StatusCode Reporter::rebuild_index_() {
    roc_panic_if(!need_rebuild_index_);

    address_index_.clear();

    // We have only one local sending stream, but in case of multicast,
    // we create multiple sending stream objects, one per each receiver.
    // We know about present receivers from RTCP reports obtained from
    // them. Here we iterate over every sending stream object for each
    // discovered receiver and add it to index. Its report will be
    // later used to generate XR packets for that specific receiver.
    if (has_local_send_report_) {
        if (participant_report_mode_ == Report_ToAddress) {
            // If there is configured single destination address for all reports,
            // ensure that it's always present in the index, even if there are
            // no sending stream objects for per-receiver reports.
            core::SharedPtr<Address> address =
                find_address_(participant_report_addr_, AutoCreate);
            if (!address) {
                return status::StatusNoMem;
            }

            rebuild_address_(*address);
        }

        for (core::SharedPtr<Stream> stream = stream_map_.front(); stream != NULL;
             stream = stream_map_.nextof(*stream)) {
            if (stream->is_looped) {
                // Skip looped back streams.
                continue;
            }
            if (!stream->has_remote_recv_report
                || stream->remote_recv_report.report_timestamp == 0) {
                // Skip sending stream if we haven't received both RR and RRTR yet.
                // Without RRTR, we don't have anything to report per-receiver.
                // In Report_ToAddress mode, we will still send SR to configured
                // address, as explained above.
                continue;
            }

            core::SharedPtr<Address> address = find_address_(
                participant_report_mode_ == Report_ToAddress ? participant_report_addr_
                                                             : stream->remote_address,
                AutoCreate);
            if (!address) {
                return status::StatusNoMem;
            }

            rebuild_address_(*address);

            if (!address->send_stream_index.push_back(stream.get())) {
                return status::StatusNoMem;
            }
        }
    }

    // We can have multiple receiving streams, one per each sender in session.
    // Here we iterate over every receiving stream reported by pipeline and
    // add it to the index. Its report will be later used to generate RR
    // packets for corresponding sender.
    for (size_t recv_idx = 0; recv_idx < local_recv_reports_.size(); recv_idx++) {
        core::SharedPtr<Stream> stream =
            find_stream_(local_recv_reports_[recv_idx].sender_source_id, AutoCreate);
        if (!stream) {
            return status::StatusNoMem;
        }

        if (stream->is_looped) {
            // Skip looped back streams.
            continue;
        }
        if (participant_report_mode_ == Report_Back
            && (!stream->has_remote_send_report
                || stream->remote_send_report.report_timestamp == 0)) {
            // In Report_Back mode, skip receiving stream if we haven't received SR yet.
            // Without SR, we don't know where to send report.
            continue;
        }

        core::SharedPtr<Address> address = find_address_(
            participant_report_mode_ == Report_ToAddress ? participant_report_addr_
                                                         : stream->remote_address,
            AutoCreate);
        if (!address) {
            return status::StatusNoMem;
        }

        rebuild_address_(*address);

        if (!address->recv_stream_index.push_back(stream.get())) {
            return status::StatusNoMem;
        }

        // Save pointer to report.
        // This report is regularly updated by query_streams_().
        // If query_streams_() invalidates pointers, it ensures that
        // rebuild_index_() will be called soon after it.
        stream->local_recv_report = &local_recv_reports_[recv_idx];
    }

    // Recently rebuilt addresses are moved to the front of the list.
    // We iterate from back to front and stop when we find first address which
    // was touched during current rebuild. We consider all addresses not
    // touched during rebuild to be not relevant anymore, and remove them.
    while (Address* address = address_lru_.back()) {
        if (address->last_rebuild == report_time_) {
            break;
        }
        remove_address_(*address);
    }

    // Finally we can populate address_index_ array with pointers to addresses,
    // for fast access by numeric index.
    if (!address_index_.grow_exp(address_map_.size())) {
        return status::StatusNoMem;
    }
    for (core::SharedPtr<Address> address = address_map_.front(); address != NULL;
         address = address_map_.nextof(*address)) {
        if (!address_index_.push_back(address.get())) {
            return status::StatusNoMem;
        }
    }

    roc_log(LogDebug, "rtcp reporter: completed index rebuild: n_streams=%lu n_addrs=%lu",
            (unsigned long)stream_map_.size(), (unsigned long)address_map_.size());

    return status::StatusOK;
}

void Reporter::detect_timeouts_() {
    // If stream was not updated after deadline (i.e. there were no new reports),
    // it should be removed.
    const core::nanoseconds_t deadline = report_time_ - config_.inactivity_timeout;

    while (Stream* stream = stream_lru_.back()) {
        // Recently updated streams are moved to the front of the list.
        // We iterate from back to front and stop when we find first stream which
        // was updated recently enough.
        if (stream->last_update > deadline) {
            break;
        }

        // If there never was a report of any type for this stream, it means that
        // we added it because we receive RTP traffic from it. Probably this stream
        // just does not support RTCP, so we don't want to time out it.
        if (!stream->has_remote_recv_report && !stream->has_remote_send_report) {
            update_stream_(*stream);
            continue;
        }

        roc_log(LogDebug,
                "rtcp reporter: report timeout expired, terminating stream:"
                " ssrc=%lu cname=%s last_update=%lld deadline=%lld timeout=%.3fms",
                (unsigned long)stream->source_id, cname_to_str(stream->cname).c_str(),
                (long long)stream->last_update, (long long)deadline,
                (double)config_.inactivity_timeout / core::Millisecond);

        {
            // If we're receiving, notify pipeline that sender timed out.
            const packet::stream_source_t send_source_id = stream->source_id;

            roc_log(LogTrace,
                    "rtcp reporter: halt_recv_stream(): send_ssrc=%lu send_cname=%s",
                    (unsigned long)send_source_id, cname_to_str(stream->cname).c_str());

            participant_.halt_recv_stream(send_source_id);
        }

        remove_stream_(*stream);
    }
}

void Reporter::detect_collision_(packet::stream_source_t source_id) {
    if (collision_detected_) {
        // Already detected collision.
        return;
    }

    if (source_id != local_source_id_) {
        // No collision.
        return;
    }

    core::SharedPtr<Stream> stream = find_stream_(source_id, NoAutoCreate);

    if (stream && stream->is_looped) {
        // We already detected that this is not a collision (see below).
        return;
    }

    if (stream && strcmp(stream->cname, local_cname_) == 0) {
        // If stream has same both SSRC and CNAME, we assume that this is our
        // own stream looped back to us because of network misconfiguration.
        // We report it once and don't consider it a collision.
        roc_log(LogDebug,
                "rtcp reporter: detected possible network loop:"
                " remote stream has same SSRC and CNAME as local: ssrc=%lu cname=%s",
                (unsigned long)stream->source_id, cname_to_str(stream->cname).c_str());
        stream->is_looped = true;
        return;
    }

    // If stream has same SSRC, but not CNAME, we assume that we found SSRC collision.
    // We should first send BYE message with old SSRC, and then change SSRC to a new
    // random value. We achieve this in two steps:
    //  - During generation, we return true from need_boodbye() to tell rtcp::Communicator
    //    to call generate_goodbye() and to send BYE message.
    //  - After generation is completed, we call resolve_collision_(), that finishes
    //    collision handling by choosing new SSRC.
    roc_log(LogDebug,
            "rtcp reporter: detected SSRC collision:"
            " remote_ssrc=%lu remote_cname=%s local_ssrc=%lu local_cname=%s",
            (unsigned long)source_id, cname_to_str(stream ? stream->cname : NULL).c_str(),
            (unsigned long)local_source_id_, cname_to_str(local_cname_).c_str());

    collision_detected_ = true;
    collision_reported_ = false;
}

void Reporter::resolve_collision_() {
    if (!collision_detected_) {
        return;
    }

    if (!collision_reported_) {
        roc_panic("rtcp reporter:"
                  " need_goodbye() returned true, but generate_goodbye() was not called");
    }

    // If we are here, it means that:
    //  - detect_collision_() detected collision
    //  - generate_goodbye() reported collision (sent BYE for old SSRC)
    // Now it's time to select a new random SSRC.
    for (;;) {
        participant_.change_source_id();

        const ParticipantInfo part_info = participant_.participant_info();

        local_source_id_ = part_info.source_id;

        if (find_stream_(local_source_id_, NoAutoCreate)) {
            // Newly selected SSRC leads to collision again. Unlikely, but possible.
            // Repeat.
            continue;
        }

        break;
    }

    roc_log(LogDebug, "rtcp reporter: changed local SSRC: ssrc=%lu cname=%s",
            (unsigned long)local_source_id_, cname_to_str(local_cname_).c_str());

    // Update SSRC in stored reports.
    for (core::SharedPtr<Stream> stream = stream_map_.front(); stream != NULL;
         stream = stream_map_.nextof(*stream)) {
        if (stream->has_remote_recv_report) {
            stream->remote_recv_report.sender_source_id = local_source_id_;
        }
    }

    collision_detected_ = false;
    collision_reported_ = false;
}

void Reporter::validate_send_report_(const SendReport& send_report) {
    if (send_report.sender_source_id != local_source_id_) {
        roc_panic("rtcp reporter:"
                  " query returned invalid sender report:"
                  " sender SSRC should be same returned by source_id():"
                  " returned_ssrc=%lu expected_ssrc=%lu",
                  (unsigned long)send_report.sender_source_id,
                  (unsigned long)local_source_id_);
    }

    if (send_report.sender_cname == NULL) {
        roc_panic("rtcp reporter:"
                  " query returned invalid sender report:"
                  " CNAME is null");
    }

    if (strcmp(send_report.sender_cname, local_cname_) != 0) {
        roc_panic("rtcp reporter:"
                  " query returned invalid sender report:"
                  " CNAME should be same as returned by cname():"
                  " returned_cname=%s expected_cname=%s",
                  cname_to_str(send_report.sender_cname).c_str(),
                  cname_to_str(local_cname_).c_str());
    }

    if (send_report.report_timestamp != report_time_) {
        roc_panic("rtcp reporter:"
                  " query returned invalid sender report:"
                  " report timestamp should be same as passed to method:"
                  " returned_ts=%lld expected_ts=%lld",
                  (long long)send_report.report_timestamp, (long long)report_time_);
    }

    if (send_report.sample_rate == 0) {
        roc_panic("rtcp reporter:"
                  " query returned invalid sender report:"
                  " sample rate should be set to non-zero value");
    }

    if (send_report.clock_offset != 0 || send_report.rtt != 0) {
        roc_panic("rtcp reporter:"
                  " query returned invalid sender report:"
                  " clock_offset and rtt are read-only fields and should not be set");
    }
}

void Reporter::validate_recv_report_(const RecvReport& recv_report) {
    if (recv_report.receiver_source_id != local_source_id_) {
        roc_panic("rtcp reporter:"
                  " query returned invalid receiver report:"
                  " SSRC should be same for all local sending and receiving streams:"
                  " returned_ssrc=%lu expected_ssrc=%lu",
                  (unsigned long)recv_report.receiver_source_id,
                  (unsigned long)local_source_id_);
    }

    if (recv_report.report_timestamp != report_time_) {
        roc_panic("rtcp reporter:"
                  " query returned invalid receiver report:"
                  " report timestamp should be same as passed to method:"
                  " returned_ts=%lld expected_ts=%lld",
                  (long long)recv_report.report_timestamp, (long long)report_time_);
    }

    if (recv_report.sample_rate == 0) {
        roc_panic("rtcp reporter:"
                  " query returned invalid receiver report:"
                  " sample rate should be set to non-zero value");
    }

    if (recv_report.clock_offset != 0 || recv_report.rtt != 0) {
        roc_panic("rtcp reporter:"
                  " query returned invalid receiver report:"
                  " clock_offset and rtt are read-only fields and should be set to zero");
    }
}

core::SharedPtr<Reporter::Stream>
Reporter::find_stream_(packet::stream_source_t source_id, CreateMode mode) {
    core::SharedPtr<Stream> stream = stream_map_.find(source_id);

    if (!stream && mode == NoAutoCreate) {
        return NULL;
    }

    if (!stream) {
        roc_log(LogDebug, "rtcp reporter: creating stream: ssrc=%lu",
                (unsigned long)source_id);

        stream =
            new (stream_pool_) Stream(stream_pool_, source_id, report_time_, config_.rtt);
        if (!stream) {
            report_error_ = status::StatusNoMem;
            return NULL;
        }

        if (!stream_map_.insert(*stream)) {
            report_error_ = status::StatusNoMem;
            return NULL;
        }

        stream_lru_.push_front(*stream);

        need_rebuild_index_ = true;
    }

    return stream;
}

void Reporter::remove_stream_(Stream& stream) {
    roc_log(LogDebug, "rtcp reporter: removing stream: ssrc=%lu",
            (unsigned long)stream.source_id);

    stream_lru_.remove(stream);
    stream_map_.remove(stream);

    need_rebuild_index_ = true;
}

void Reporter::update_stream_(Stream& stream) {
    if (stream.last_update == report_time_) {
        return;
    }

    stream.last_update = report_time_;

    stream_lru_.remove(stream);
    stream_lru_.push_front(stream);
}

core::SharedPtr<Reporter::Address>
Reporter::find_address_(const address::SocketAddr& remote_address, CreateMode mode) {
    core::SharedPtr<Address> address = address_map_.find(remote_address);

    if (!address && mode == NoAutoCreate) {
        return NULL;
    }

    if (!address) {
        roc_log(LogDebug, "rtcp reporter: creating address: remote_addr=%s",
                address::socket_addr_to_str(remote_address).c_str());

        address = new (address_pool_)
            Address(address_pool_, arena_, remote_address, report_time_);
        if (!address) {
            report_error_ = status::StatusNoMem;
            return NULL;
        }

        if (!address_map_.insert(*address)) {
            report_error_ = status::StatusNoMem;
            return NULL;
        }

        address_lru_.push_front(*address);
    }

    return address;
}

void Reporter::remove_address_(Address& address) {
    roc_log(LogDebug,
            "rtcp reporter: removing address:"
            " remote_addr=%s n_send_streams=%lu n_recv_streams=%lu",
            address::socket_addr_to_str(address.remote_address).c_str(),
            (unsigned long)address.send_stream_index.size(),
            (unsigned long)address.recv_stream_index.size());

    address_lru_.remove(address);
    address_map_.remove(address);
}

void Reporter::rebuild_address_(Address& address) {
    if (address.last_rebuild == report_time_) {
        // Already called during current rebuild.
        return;
    }

    // First call during current rebuild.
    address.last_rebuild = report_time_;

    address_lru_.remove(address);
    address_lru_.push_front(address);

    // Clear streams from previous rebuild.
    address.send_stream_index.clear();
    address.recv_stream_index.clear();
}

} // namespace rtcp
} // namespace roc
