/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#include "util/legacy_checksum.h"

#include <stdint.h>
#include <string.h>

// Software implementation of the legacy checksum. This emulates the behaviour
// of the CRC peripheral in the STM32F2/F4 series MCUs and the bugs in the
// legacy CRC driver implementation. While the raw throughput of the hardware
// CRC peripheral is greater, it is not reentrant and so a mutex is required
// to prevent concurrent access to the peripheral by multiple tasks. The
// overhead of locking and unlocking the mutex, while not yet benchmarked, is
// potentially significant enough to cancel out the increased throughput of
// the hardware acceleration. There is also no evidence (for or against) that
// checksums are a performance bottleneck in the first place. Given the added
// complexity of the hardware-accelerated implementation, and the dubious
// performance improvement of using it, hardware acceleration of checksums is a
// case of premature optimization.
//
// That being said, the API for the legacy checksum is fully capable of
// supporting a non-reentrant implementation. Simply acquire the mutex in
// legacy_defective_checksum_init and release it in
// legacy_defective_checksum_finish.

// The implementation is based on a nybble-wide table driven CRC.
// The CRC implementation (but not the checksum based on it) has the
// model parameters:
//   Width: 32
//   Poly: 04C11DB7
//   Init: FFFFFFFF
//   RefIn: False
//   RefOut: False
//   XorOut: 00000000
//
// The CRC lookup table was generated by legacy_checksum_crc_table.py

static const uint32_t s_lookup_table[] = {
  0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
  0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
  0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
  0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
};

static uint32_t prv_crc_byte(uint32_t crc, uint8_t input) {
  crc = (crc << 4) ^ s_lookup_table[((crc >> 28) ^ (input >> 4)) & 0x0f];
  crc = (crc << 4) ^ s_lookup_table[((crc >> 28) ^ (input >> 0)) & 0x0f];
  return crc;
}

void legacy_defective_checksum_init(LegacyChecksum *checksum) {
  *checksum = (LegacyChecksum) {
    .reg = 0xffffffff,
  };
}

void legacy_defective_checksum_update(
    LegacyChecksum * restrict checksum,
    const void * restrict data, size_t length) {
  const char * restrict data_bytes = data;
  uint32_t * restrict reg = &checksum->reg;
  uint8_t * restrict accumulator = checksum->accumulator;
  uint8_t * restrict accumulated_length = &checksum->accumulated_length;

  if (*accumulated_length) {
    for (; *accumulated_length < 3 && length; length--) {
      accumulator[(*accumulated_length)++] = *data_bytes++;
    }

    if (*accumulated_length == 3 && length) {
      *reg = prv_crc_byte(*reg, *data_bytes++);
      length--;
      *reg = prv_crc_byte(*reg, accumulator[2]);
      *reg = prv_crc_byte(*reg, accumulator[1]);
      *reg = prv_crc_byte(*reg, accumulator[0]);
      *accumulated_length = 0;
    }
  }

  for (; length >= 4; length -= 4) {
    *reg = prv_crc_byte(*reg, data_bytes[3]);
    *reg = prv_crc_byte(*reg, data_bytes[2]);
    *reg = prv_crc_byte(*reg, data_bytes[1]);
    *reg = prv_crc_byte(*reg, data_bytes[0]);
    data_bytes += 4;
  }

  for (; length; length--) {
    accumulator[(*accumulated_length)++] = *data_bytes++;
  }
}

uint32_t legacy_defective_checksum_finish(LegacyChecksum *checksum) {
  if (checksum->accumulated_length) {
    // CRC the final bytes forwards (reversed relative to the normal checksum)
    // padded on the left(!) with null bytes.
    for (int padding = 4 - checksum->accumulated_length; padding; padding--) {
      checksum->reg = prv_crc_byte(checksum->reg, 0);
    }
    for (int i = 0; i < checksum->accumulated_length; ++i) {
      checksum->reg = prv_crc_byte(checksum->reg, checksum->accumulator[i]);
    }
  }

  return checksum->reg;
}

uint32_t legacy_defective_checksum_memory(const void * restrict data,
                                          size_t length) {
  LegacyChecksum checksum;
  legacy_defective_checksum_init(&checksum);
  legacy_defective_checksum_update(&checksum, data, length);
  return legacy_defective_checksum_finish(&checksum);
}
