text: // Copyright 2017 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "debuglog.h"
  
  #include <inttypes.h>
  #include <lib/zx/clock.h>
  #include <lib/zx/debuglog.h>
  #include <stdio.h>
  #include <string.h>
  #include <zircon/boot/netboot.h>
  #include <zircon/syscalls.h>
  #include <zircon/syscalls/log.h>
  
  #include "netsvc.h"
  #include "tftp.h"
  
  #define MAX_LOG_LINE (ZX_LOG_RECORD_MAX + 32)
  
  static zx::debuglog debuglog;
  static logpacket_t pkt;
  static size_t pkt_len;
  
  static volatile uint32_t seqno = 1;
  static volatile uint32_t pending = 0;
  
  static zx_time_t g_debuglog_next_timeout = ZX_TIME_INFINITE;
  
  zx_time_t debuglog_next_timeout() { return g_debuglog_next_timeout; }
  
  #define SEND_DELAY_SHORT ZX_MSEC(100)
  #define SEND_DELAY_LONG ZX_SEC(4)
  
  // Number of consecutive unacknowledged packets we will send before reducing send rate.
  static const unsigned kUnackedThreshold = 5;
  
  // Number of consecutive packets that went unacknowledged. Is reset on acknowledgment.
  static unsigned num_unacked = 0;
  
  // How long to wait between sending.
  static zx_duration_t send_delay = SEND_DELAY_SHORT;
  
  static size_t get_log_line(char* out) {
    char buf[ZX_LOG_RECORD_MAX + 1];
    zx_log_record_t* rec = reinterpret_cast<zx_log_record_t*>(buf);
    for (;;) {
      if (debuglog.read(0, rec, ZX_LOG_RECORD_MAX) > 0) {
        if (rec->datalen && (rec->data[rec->datalen - 1] == '\n')) {
          rec->datalen--;
        }
        // records flagged for local display are ignored
        if (rec->flags & ZX_LOG_LOCAL) {
          continue;
        }
        rec->data[rec->datalen] = 0;
        snprintf(out, MAX_LOG_LINE, "[%05d.%03d] %05" PRIu64 ".%05" PRIu64 "> %s\n",
                 static_cast<int>(rec->timestamp / 1000000000ULL),
                 static_cast<int>((rec->timestamp / 1000000ULL) % 1000ULL), rec->pid, rec->tid,
                 rec->data);
        return strlen(out);
      } else {
        return 0;
      }
    }
  }
  
  int debuglog_init() {
    if (zx::debuglog::create(zx::resource(), ZX_LOG_FLAG_READABLE, &debuglog) < 0) {
      return -1;
    }
  
    // Set up our timeout to expire immediately, so that we check for pending log messages
    g_debuglog_next_timeout = zx::clock::get_monotonic().get();
  
    seqno = 1;
    pending = 0;
  
    return 0;
  }
  
  // If we have an outstanding (unacknowledged) log, resend it. Otherwise, send new logs, if we
  // have any.
  static void debuglog_send() {
    if (pending == 0) {
      pkt.magic = NB_DEBUGLOG_MAGIC;
      pkt.seqno = seqno;
      strncpy(pkt.nodename, nodename(), sizeof(pkt.nodename) - 1);
      pkt_len = 0;
      while (pkt_len < (MAX_LOG_DATA - MAX_LOG_LINE)) {
        size_t r = get_log_line(pkt.data + pkt_len);
        if (r > 0) {
          pkt_len += r;
        } else {
          break;
        }
      }
      if (pkt_len) {
        // include header and nodename in length
        pkt_len += MAX_NODENAME_LENGTH + sizeof(uint32_t) * 2;
        pending = 1;
      } else {
        goto done;
      }
    }
    udp6_send(&pkt, pkt_len, &ip6_ll_all_nodes, DEBUGLOG_PORT, DEBUGLOG_ACK_PORT, false);
  done:
    g_debuglog_next_timeout = zx_deadline_after(send_delay);
  }
  
  void debuglog_recv(void* data, size_t len, bool is_mcast) {
    // The only message we should be receiving is acknowledgement of our last transmission
    if (!pending) {
      return;
    }
    if ((len != 8) || is_mcast) {
      return;
    }
    // Copied not cast in-place to satisfy alignment requirements flagged by ubsan (see fxbug.dev/45798).
    logpacket_t pkt;
    memcpy(&pkt, data, sizeof(logpacket_t));
    if ((pkt.magic != NB_DEBUGLOG_MAGIC) || (pkt.seqno != seqno)) {
      return;
    }
  
    // Received an ack. We have an active listener. Don't delay.
    num_unacked = 0;
    send_delay = SEND_DELAY_SHORT;
  
    seqno++;
    pending = 0;
    debuglog_send();
  }
  
  void debuglog_timeout_expired() {
    if (pending) {
      // No reply. If noone is listening, reduce send rate.
      if (++num_unacked >= kUnackedThreshold) {
        send_delay = SEND_DELAY_LONG;
      }
    }
    debuglog_send();
  }
  