text: /*
   * Copyright (c) 2018 Nordic Semiconductor ASA
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #include <logging/log_output.h>
  #include <logging/log_ctrl.h>
  #include <logging/log.h>
  #include <sys/__assert.h>
  #include <sys/cbprintf.h>
  #include <ctype.h>
  #include <time.h>
  #include <stdio.h>
  #include <stdbool.h>
  
  #define LOG_COLOR_CODE_DEFAULT "\x1B[0m"
  #define LOG_COLOR_CODE_RED     "\x1B[1;31m"
  #define LOG_COLOR_CODE_YELLOW  "\x1B[1;33m"
  
  #define HEXDUMP_BYTES_IN_LINE 16
  
  #define  DROPPED_COLOR_PREFIX \
  	Z_LOG_EVAL(CONFIG_LOG_BACKEND_SHOW_COLOR, (LOG_COLOR_CODE_RED), ())
  
  #define DROPPED_COLOR_POSTFIX \
  	Z_LOG_EVAL(CONFIG_LOG_BACKEND_SHOW_COLOR, (LOG_COLOR_CODE_DEFAULT), ())
  
  static const char *const severity[] = {
  	NULL,
  	"err",
  	"wrn",
  	"inf",
  	"dbg"
  };
  
  static const char *const colors[] = {
  	NULL,
  	LOG_COLOR_CODE_RED,     /* err */
  	LOG_COLOR_CODE_YELLOW,  /* warn */
  	NULL,                   /* info */
  	NULL                    /* dbg */
  };
  
  static uint32_t freq;
  static uint32_t timestamp_div;
  
  extern void log_output_msg_syst_process(const struct log_output *output,
  				struct log_msg *msg, uint32_t flag);
  extern void log_output_string_syst_process(const struct log_output *output,
  				struct log_msg_ids src_level,
  				const char *fmt, va_list ap, uint32_t flag);
  extern void log_output_hexdump_syst_process(const struct log_output *output,
  				struct log_msg_ids src_level,
  				const uint8_t *data, uint32_t length, uint32_t flag);
  
  /* The RFC 5424 allows very flexible mapping and suggest the value 0 being the
   * highest severity and 7 to be the lowest (debugging level) severity.
   *
   *    0   Emergency      System is unusable
   *    1   Alert          Action must be taken immediately
   *    2   Critical       Critical conditions
   *    3   Error          Error conditions
   *    4   Warning        Warning conditions
   *    5   Notice         Normal but significant condition
   *    6   Informational  Informational messages
   *    7   Debug          Debug-level messages
   */
  static int level_to_rfc5424_severity(uint32_t level)
  {
  	uint8_t ret;
  
  	switch (level) {
  	case LOG_LEVEL_NONE:
  		ret = 7U;
  		break;
  	case LOG_LEVEL_ERR:
  		ret =  3U;
  		break;
  	case LOG_LEVEL_WRN:
  		ret =  4U;
  		break;
  	case LOG_LEVEL_INF:
  		ret =  6U;
  		break;
  	case LOG_LEVEL_DBG:
  		ret = 7U;
  		break;
  	default:
  		ret = 7U;
  		break;
  	}
  
  	return ret;
  }
  
  static int out_func(int c, void *ctx)
  {
  	const struct log_output *out_ctx = (const struct log_output *)ctx;
  	int idx;
  
  	if (IS_ENABLED(CONFIG_LOG_IMMEDIATE)) {
  		/* Backend must be thread safe in synchronous operation. */
  		out_ctx->func((uint8_t *)&c, 1, out_ctx->control_block->ctx);
  		return 0;
  	}
  
  	if (out_ctx->control_block->offset == out_ctx->size) {
  		log_output_flush(out_ctx);
  	}
  
  	idx = atomic_inc(&out_ctx->control_block->offset);
  	out_ctx->buf[idx] = (uint8_t)c;
  
  	__ASSERT_NO_MSG(out_ctx->control_block->offset <= out_ctx->size);
  
  	return 0;
  }
  
  static int cr_out_func(int c, void *ctx)
  {
  	out_func(c, ctx);
  	if (c == '\n') {
  		out_func((int)'\r', ctx);
  	}
  
  	return 0;
  }
  
  static int print_formatted(const struct log_output *output,
  			   const char *fmt, ...)
  {
  	va_list args;
  	int length = 0;
  
  	va_start(args, fmt);
  	length = cbvprintf(out_func, (void *)output, fmt, args);
  	va_end(args);
  
  	return length;
  }
  
  static void buffer_write(log_output_func_t outf, uint8_t *buf, size_t len,
  			 void *ctx)
  {
  	int processed;
  
  	do {
  		processed = outf(buf, len, ctx);
  		len -= processed;
  		buf += processed;
  	} while (len != 0);
  }
  
  
  void log_output_flush(const struct log_output *output)
  {
  	buffer_write(output->func, output->buf,
  		     output->control_block->offset,
  		     output->control_block->ctx);
  
  	output->control_block->offset = 0;
  }
  
  static int timestamp_print(const struct log_output *output,
  			   uint32_t flags, uint32_t timestamp)
  {
  	int length;
  	bool format =
  		(flags & LOG_OUTPUT_FLAG_FORMAT_TIMESTAMP) |
  		(flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG);
  
  
  	if (!format) {
  		length = print_formatted(output, "[%08lu] ", timestamp);
  	} else if (freq != 0U) {
  		uint32_t total_seconds;
  		uint32_t remainder;
  		uint32_t seconds;
  		uint32_t hours;
  		uint32_t mins;
  		uint32_t ms;
  		uint32_t us;
  
  		timestamp /= timestamp_div;
  		total_seconds = timestamp / freq;
  		seconds = total_seconds;
  		hours = seconds / 3600U;
  		seconds -= hours * 3600U;
  		mins = seconds / 60U;
  		seconds -= mins * 60U;
  
  		remainder = timestamp % freq;
  		ms = (remainder * 1000U) / freq;
  		us = (1000 * (remainder * 1000U - (ms * freq))) / freq;
  
  		if (IS_ENABLED(CONFIG_LOG_BACKEND_NET) &&
  		    flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG) {
  #if defined(CONFIG_NEWLIB_LIBC)
  			char time_str[sizeof("1970-01-01T00:00:00")];
  			struct tm *tm;
  			time_t time;
  
  			time = total_seconds;
  			tm = gmtime(&time);
  
  			strftime(time_str, sizeof(time_str), "%FT%T", tm);
  
  			length = print_formatted(output, "%s.%06uZ ",
  						 time_str, ms * 1000U + us);
  #else
  			length = print_formatted(output,
  					"1970-01-01T%02u:%02u:%02u.%06uZ ",
  					hours, mins, seconds, ms * 1000U + us);
  #endif
  		} else {
  			length = print_formatted(output,
  						 "[%02u:%02u:%02u.%03u,%03u] ",
  						 hours, mins, seconds, ms, us);
  		}
  	} else {
  		length = 0;
  	}
  
  	return length;
  }
  
  static void color_print(const struct log_output *output,
  			bool color, bool start, uint32_t level)
  {
  	if (color) {
  		const char *log_color = start && (colors[level] != NULL) ?
  				colors[level] : LOG_COLOR_CODE_DEFAULT;
  		print_formatted(output, "%s", log_color);
  	}
  }
  
  static void color_prefix(const struct log_output *output,
  			 bool color, uint32_t level)
  {
  	color_print(output, color, true, level);
  }
  
  static void color_postfix(const struct log_output *output,
  			  bool color, uint32_t level)
  {
  	color_print(output, color, false, level);
  }
  
  
  static int ids_print(const struct log_output *output, bool level_on,
  		     bool func_on, uint32_t domain_id, int16_t source_id,
  		     uint32_t level)
  {
  	int total = 0;
  
  	if (level_on) {
  		total += print_formatted(output, "<%s> ", severity[level]);
  	}
  
  	if (source_id >= 0) {
  		total += print_formatted(output,
  				(func_on &&
  				((1 << level) & LOG_FUNCTION_PREFIX_MASK)) ?
  				"%s." : "%s: ",
  				log_source_name_get(domain_id, source_id));
  	}
  
  	return total;
  }
  
  static void newline_print(const struct log_output *ctx, uint32_t flags)
  {
  	if (IS_ENABLED(CONFIG_LOG_BACKEND_NET) &&
  	    flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG) {
  		return;
  	}
  
  	if ((flags & LOG_OUTPUT_FLAG_CRLF_NONE) != 0U) {
  		return;
  	}
  
  	if ((flags & LOG_OUTPUT_FLAG_CRLF_LFONLY) != 0U) {
  		print_formatted(ctx, "\n");
  	} else {
  		print_formatted(ctx, "\r\n");
  	}
  }
  
  static void std_print(struct log_msg *msg,
  		      const struct log_output *output)
  {
  	const char *str = log_msg_str_get(msg);
  	uint32_t nargs = log_msg_nargs_get(msg);
  	log_arg_t *args = alloca(sizeof(log_arg_t)*nargs);
  	int i;
  
  	for (i = 0; i < nargs; i++) {
  		args[i] = log_msg_arg_get(msg, i);
  	}
  
  	switch (log_msg_nargs_get(msg)) {
  	case 0:
  		print_formatted(output, str);
  		break;
  	case 1:
  		print_formatted(output, str, args[0]);
  		break;
  	case 2:
  		print_formatted(output, str, args[0], args[1]);
  		break;
  	case 3:
  		print_formatted(output, str, args[0], args[1], args[2]);
  		break;
  	case 4:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3]);
  		break;
  	case 5:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4]);
  		break;
  	case 6:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5]);
  		break;
  	case 7:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6]);
  		break;
  	case 8:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6], args[7]);
  		break;
  	case 9:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6],  args[7],
  				args[8]);
  		break;
  	case 10:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6],  args[7],
  				args[8], args[9]);
  		break;
  	case 11:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6],  args[7],
  				args[8], args[9], args[10]);
  		break;
  	case 12:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6],  args[7],
  				args[8], args[9], args[10], args[11]);
  		break;
  	case 13:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6],  args[7],
  				args[8], args[9], args[10], args[11], args[12]);
  		break;
  	case 14:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6],  args[7],
  				args[8], args[9], args[10], args[11], args[12],
  				args[13]);
  		break;
  	case 15:
  		print_formatted(output, str, args[0], args[1], args[2],
  				args[3], args[4], args[5], args[6],  args[7],
  				args[8], args[9], args[10], args[11], args[12],
  				args[13], args[14]);
  		break;
  	default:
  		/* Unsupported number of arguments. */
  		__ASSERT_NO_MSG(true);
  		break;
  	}
  }
  
  static void hexdump_line_print(const struct log_output *output,
  			       const uint8_t *data, uint32_t length,
  			       int prefix_offset, uint32_t flags)
  {
  	newline_print(output, flags);
  
  	for (int i = 0; i < prefix_offset; i++) {
  		print_formatted(output, " ");
  	}
  
  	for (int i = 0; i < HEXDUMP_BYTES_IN_LINE; i++) {
  		if (i > 0 && !(i % 8)) {
  			print_formatted(output, " ");
  		}
  
  		if (i < length) {
  			print_formatted(output, "%02x ", data[i]);
  		} else {
  			print_formatted(output, "   ");
  		}
  	}
  
  	print_formatted(output, "|");
  
  	for (int i = 0; i < HEXDUMP_BYTES_IN_LINE; i++) {
  		if (i > 0 && !(i % 8)) {
  			print_formatted(output, " ");
  		}
  
  		if (i < length) {
  			char c = (char)data[i];
  
  			print_formatted(output, "%c",
  			      isprint((int)c) ? c : '.');
  		} else {
  			print_formatted(output, " ");
  		}
  	}
  }
  
  static void hexdump_print(struct log_msg *msg,
  			  const struct log_output *output,
  			  int prefix_offset, uint32_t flags)
  {
  	uint32_t offset = 0U;
  	uint8_t buf[HEXDUMP_BYTES_IN_LINE];
  	size_t length;
  
  	print_formatted(output, "%s", log_msg_str_get(msg));
  
  	do {
  		length = sizeof(buf);
  		log_msg_hexdump_data_get(msg, buf, &length, offset);
  
  		if (length) {
  			hexdump_line_print(output, buf, length,
  					   prefix_offset, flags);
  			offset += length;
  		} else {
  			break;
  		}
  	} while (true);
  }
  
  static void log_msg2_hexdump(const struct log_output *output,
  			     uint8_t *data, uint32_t len,
  			     int prefix_offset, uint32_t flags)
  {
  	size_t length;
  
  	do {
  		length = MIN(len, HEXDUMP_BYTES_IN_LINE);
  
  		hexdump_line_print(output, data, length,
  				   prefix_offset, flags);
  		data += length;
  		len -= length;
  	} while (len);
  }
  
  
  static void raw_string_print(struct log_msg *msg,
  			     const struct log_output *output)
  {
  	__ASSERT_NO_MSG(output->size);
  
  	size_t offset = 0;
  	size_t length;
  	bool eol = false;
  
  	do {
  		length = output->size;
  		/* Sting is stored in a hexdump message. */
  		log_msg_hexdump_data_get(msg, output->buf, &length, offset);
  		output->control_block->offset = length;
  
  		if (length != 0) {
  			eol = (output->buf[length - 1] == '\n');
  		}
  
  		log_output_flush(output);
  		offset += length;
  	} while (length > 0);
  
  	if (eol) {
  		print_formatted(output, "\r");
  	}
  }
  
  static uint32_t prefix_print(const struct log_output *output,
  			 uint32_t flags, bool func_on, uint32_t timestamp, uint8_t level,
  			 uint8_t domain_id, int16_t source_id)
  {
  	uint32_t length = 0U;
  
  	bool stamp = flags & LOG_OUTPUT_FLAG_TIMESTAMP;
  	bool colors_on = flags & LOG_OUTPUT_FLAG_COLORS;
  	bool level_on = flags & LOG_OUTPUT_FLAG_LEVEL;
  
  	if (IS_ENABLED(CONFIG_LOG_BACKEND_NET) &&
  	    flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG) {
  		/* TODO: As there is no way to figure out the
  		 * facility at this point, use a pre-defined value.
  		 * Change this to use the real facility of the
  		 * logging call when that info is available.
  		 */
  		static const int facility = 16; /* local0 */
  
  		length += print_formatted(
  			output,
  			"<%d>1 ",
  			facility * 8 +
  			level_to_rfc5424_severity(level));
  	}
  
  	if (stamp) {
  		length += timestamp_print(output, flags, timestamp);
  	}
  
  	if (IS_ENABLED(CONFIG_LOG_BACKEND_NET) &&
  	    flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG) {
  		length += print_formatted(
  			output, "%s - - - - ",
  			output->control_block->hostname ?
  			output->control_block->hostname :
  			"zephyr");
  	} else {
  		color_prefix(output, colors_on, level);
  	}
  
  	length += ids_print(output, level_on, func_on,
  			domain_id, source_id, level);
  
  
  	return length;
  }
  
  static void postfix_print(const struct log_output *output,
  			  uint32_t flags, uint8_t level)
  {
  	color_postfix(output, (flags & LOG_OUTPUT_FLAG_COLORS),
  			      level);
  	newline_print(output, flags);
  }
  
  void log_output_msg_process(const struct log_output *output,
  			    struct log_msg *msg,
  			    uint32_t flags)
  {
  	bool std_msg = log_msg_is_std(msg);
  	uint32_t timestamp = log_msg_timestamp_get(msg);
  	uint8_t level = (uint8_t)log_msg_level_get(msg);
  	uint8_t domain_id = (uint8_t)log_msg_domain_id_get(msg);
  	int16_t source_id = (int16_t)log_msg_source_id_get(msg);
  	bool raw_string = (level == LOG_LEVEL_INTERNAL_RAW_STRING);
  	int prefix_offset;
  
  	if (IS_ENABLED(CONFIG_LOG_MIPI_SYST_ENABLE) &&
  	    flags & LOG_OUTPUT_FLAG_FORMAT_SYST) {
  		log_output_msg_syst_process(output, msg, flags);
  		return;
  	}
  
  	prefix_offset = raw_string ?
  			0 : prefix_print(output, flags, std_msg, timestamp,
  					 level, domain_id, source_id);
  
  	if (log_msg_is_std(msg)) {
  		std_print(msg, output);
  	} else if (raw_string) {
  		raw_string_print(msg, output);
  	} else {
  		hexdump_print(msg, output, prefix_offset, flags);
  	}
  
  	if (!raw_string) {
  		postfix_print(output, flags, level);
  	}
  
  	log_output_flush(output);
  }
  
  void log_output_msg2_process(const struct log_output *output,
  			     struct log_msg2 *msg, uint32_t flags)
  {
  	log_timestamp_t timestamp = log_msg2_get_timestamp(msg);
  	uint8_t level = log_msg2_get_level(msg);
  	bool raw_string = (level == LOG_LEVEL_INTERNAL_RAW_STRING);
  	uint32_t prefix_offset;
  
  	if (IS_ENABLED(CONFIG_LOG_MIPI_SYST_ENABLE) &&
  	    flags & LOG_OUTPUT_FLAG_FORMAT_SYST) {
  		__ASSERT_NO_MSG(0);
  		/* todo not supported
  		 * log_output_msg_syst_process(output, msg, flags);
  		 */
  		return;
  	}
  
  	if (!raw_string) {
  		void *source = (void *)log_msg2_get_source(msg);
  		uint8_t domain_id = log_msg2_get_domain(msg);
  		int16_t source_id = source ?
  			(IS_ENABLED(CONFIG_LOG_RUNTIME_FILTERING) ?
  				log_dynamic_source_id(source) :
  				log_const_source_id(source)) :
  			-1;
  
  		prefix_offset = prefix_print(output, flags, 0, timestamp,
  					 level, domain_id, source_id);
  	} else {
  		prefix_offset = 0;
  	}
  
  	size_t len;
  	uint8_t *data = log_msg2_get_package(msg, &len);
  
  	if (len) {
  		int err = cbpprintf(raw_string ? cr_out_func :  out_func,
  				    (void *)output, data);
  
  		(void)err;
  		__ASSERT_NO_MSG(err >= 0);
  	}
  
  	data = log_msg2_get_data(msg, &len);
  	if (len) {
  		log_msg2_hexdump(output, data, len, prefix_offset, flags);
  	}
  
  	if (!raw_string) {
  		postfix_print(output, flags, level);
  	}
  
  	log_output_flush(output);
  }
  
  static bool ends_with_newline(const char *fmt)
  {
  	char c = '\0';
  
  	while (*fmt != '\0') {
  		c = *fmt;
  		fmt++;
  	}
  
  	return (c == '\n');
  }
  
  void log_output_string(const struct log_output *output,
  		       struct log_msg_ids src_level, uint32_t timestamp,
  		       const char *fmt, va_list ap, uint32_t flags)
  {
  	int length;
  	uint8_t level = (uint8_t)src_level.level;
  	uint8_t domain_id = (uint8_t)src_level.domain_id;
  	int16_t source_id = (int16_t)src_level.source_id;
  	bool raw_string = (level == LOG_LEVEL_INTERNAL_RAW_STRING);
  
  	if (IS_ENABLED(CONFIG_LOG_MIPI_SYST_ENABLE) &&
  	    flags & LOG_OUTPUT_FLAG_FORMAT_SYST) {
  		log_output_string_syst_process(output,
  				src_level, fmt, ap, flags);
  		return;
  	}
  
  	if (!raw_string) {
  		prefix_print(output, flags, true, timestamp,
  				level, domain_id, source_id);
  	}
  
  	length = cbvprintf(out_func, (void *)output, fmt, ap);
  
  	(void)length;
  
  	if (raw_string) {
  		/* add \r if string ends with newline. */
  		if (ends_with_newline(fmt)) {
  			print_formatted(output, "\r");
  		}
  	} else {
  		postfix_print(output, flags, level);
  	}
  
  	log_output_flush(output);
  }
  
  void log_output_hexdump(const struct log_output *output,
  			     struct log_msg_ids src_level, uint32_t timestamp,
  			     const char *metadata, const uint8_t *data,
  			     uint32_t length, uint32_t flags)
  {
  	uint32_t prefix_offset;
  	uint8_t level = (uint8_t)src_level.level;
  	uint8_t domain_id = (uint8_t)src_level.domain_id;
  	int16_t source_id = (int16_t)src_level.source_id;
  
  	if (IS_ENABLED(CONFIG_LOG_MIPI_SYST_ENABLE) &&
  	    flags & LOG_OUTPUT_FLAG_FORMAT_SYST) {
  		log_output_hexdump_syst_process(output,
  				src_level, data, length, flags);
  		return;
  	}
  
  	prefix_offset = prefix_print(output, flags, true, timestamp,
  				     level, domain_id, source_id);
  
  	/* Print metadata */
  	print_formatted(output, "%s", metadata);
  
  	while (length != 0U) {
  		uint32_t part_len = length > HEXDUMP_BYTES_IN_LINE ?
  				HEXDUMP_BYTES_IN_LINE : length;
  
  		hexdump_line_print(output, data, part_len,
  				   prefix_offset, flags);
  
  		data += part_len;
  		length -= part_len;
  	}
  
  	postfix_print(output, flags, level);
  	log_output_flush(output);
  }
  
  void log_output_dropped_process(const struct log_output *output, uint32_t cnt)
  {
  	char buf[5];
  	int len;
  	static const char prefix[] = DROPPED_COLOR_PREFIX "--- ";
  	static const char postfix[] =
  			" messages dropped ---\r\n" DROPPED_COLOR_POSTFIX;
  	log_output_func_t outf = output->func;
  
  	cnt = MIN(cnt, 9999);
  	len = snprintk(buf, sizeof(buf), "%d", cnt);
  
  	buffer_write(outf, (uint8_t *)prefix, sizeof(prefix) - 1,
  		     output->control_block->ctx);
  	buffer_write(outf, buf, len, output->control_block->ctx);
  	buffer_write(outf, (uint8_t *)postfix, sizeof(postfix) - 1,
  		     output->control_block->ctx);
  }
  
  void log_output_timestamp_freq_set(uint32_t frequency)
  {
  	timestamp_div = 1U;
  	/* There is no point to have frequency higher than 1MHz (ns are not
  	 * printed) and too high frequency leads to overflows in calculations.
  	 */
  	while (frequency > 1000000) {
  		frequency /= 2U;
  		timestamp_div *= 2U;
  	}
  
  	freq = frequency;
  }
  
  uint64_t log_output_timestamp_to_us(uint32_t timestamp)
  {
  	timestamp /= timestamp_div;
  
  	return ((uint64_t) timestamp * 1000000U) / freq;
  }
  