/* Shared UkiBootCtrl definitions
   Copyright (C) 2025 Alexander Larsson <alexl@redhat.com>

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.

   This file is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "utils.h"

typedef struct attr_packed {
    uint8_t priority;
    uint8_t tries_remaining;
    uint8_t successful_boot;
    uint8_t reserved;
} UkiBootSlotInfo;

#define UKI_BOOT_CTRL_MAGIC 1420550408

typedef struct attr_packed {
    uint32_t magic;
    UkiBootSlotInfo slots[2];
    uint8_t reserved[8];
    uint32_t CRC32;
} UkiBootCtrl;

static inline bool
bootctl_slot_valid(UkiBootSlotInfo *slot)
{
    return slot->successful_boot != 0 || slot->tries_remaining > 0;
}

static inline bool
bootctl_validate(UkiBootCtrl *bootctl)
{
    if (le32toh(bootctl->magic) != UKI_BOOT_CTRL_MAGIC) {
	verbose("Invalid bootctl magic value\n");
	return false;
    }

    uint32_t crc = crc32((uint8_t *)bootctl, sizeof(UkiBootCtrl) - sizeof(uint32_t));

    if (le32toh(bootctl->CRC32) != crc) {
	verbose("Invalid bootctl crc32\n");
	return false;
    }

    return true;
}

static inline bool
bootctl_find_active_slot(UkiBootCtrl *bootctl, uint32_t *out_slot)
{
    int32_t active_slot = -1;
    for (int32_t i = 0; i < 2; i++) {
	if (bootctl_slot_valid(&bootctl->slots[i])) {
	    if (active_slot == -1) {
		active_slot = i;
	    } else {
		if (bootctl->slots[i].priority > bootctl->slots[active_slot].priority) {
		    active_slot = i;
		}
	    }
	}
    }

    if (active_slot != -1) {
	*out_slot = active_slot;
	return true;
    }

    return false;
}

static inline void
bootctl_init_default(UkiBootCtrl *bootctl)
{
    memset(bootctl, 0, sizeof(UkiBootCtrl));
    bootctl->magic = htole32(UKI_BOOT_CTRL_MAGIC);
    bootctl->slots[0].priority = 15;
    bootctl->slots[0].tries_remaining = 7;
    bootctl->slots[1].priority = 14;
    bootctl->slots[1].tries_remaining = 7;
}
