git_bug/replica/entity/
nonce.rs

1// git-bug-rs - A rust library for interfacing with git-bug repositories
2//
3// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
4// SPDX-License-Identifier: GPL-3.0-or-later
5//
6// This file is part of git-bug-rs/git-gub.
7//
8// You should have received a copy of the License along with this program.
9// If not, see <https://www.gnu.org/licenses/agpl.txt>.
10
11//! Handling for the Nonce value used to make each
12//! [`Operation`][`crate::replica::entity::operation::Operation`] unique.
13
14use base64::{Engine, prelude::BASE64_STANDARD};
15use serde::{Deserialize, Serialize};
16
17/// A garbage value, to ensure unique IDs.
18/// Git-bug creates nonces from 20 < len < 64.
19/// Thus store 64 bytes, as this will always suffice.
20#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
21pub(crate) struct Nonce {
22    // HACK(@bpeetz): We split the [_;64] up into two parts, as serde does not support arrays
23    // longer than 64 elements. <2025-06-06>
24    first: [Option<u8>; 32],
25    second: [Option<u8>; 32],
26}
27
28impl TryFrom<String> for Nonce {
29    type Error = base64::DecodeSliceError;
30
31    fn try_from(value: String) -> Result<Self, Self::Error> {
32        <Self as TryFrom<&str>>::try_from(&value)
33    }
34}
35impl TryFrom<&str> for Nonce {
36    type Error = base64::DecodeSliceError;
37
38    fn try_from(value: &str) -> Result<Self, Self::Error> {
39        // The Nonce is base64 encoded
40
41        let mut buffer = [0; 64];
42
43        let written = BASE64_STANDARD.decode_slice(value, &mut buffer)?;
44
45        let mut output = [None; 64];
46        for (slot, byte) in output.iter_mut().zip(buffer[..written].iter()) {
47            *slot = Some(*byte);
48        }
49
50        let mut first = [None; 32];
51        first.copy_from_slice(&output[..32]);
52
53        let mut second = [None; 32];
54        second.copy_from_slice(&output[32..]);
55
56        Ok(Self { first, second })
57    }
58}
59
60impl From<Nonce> for String {
61    fn from(value: Nonce) -> Self {
62        let mut buffer = String::new();
63
64        let mut index = 0;
65        let mut payload = [0; 64];
66        for (slot, maybe_byte) in payload
67            .iter_mut()
68            .zip(value.first.iter().chain(value.second.iter()))
69        {
70            if let Some(byte) = maybe_byte {
71                *slot = *byte;
72                index += 1;
73            }
74        }
75
76        let trimmed_payload = &payload[..index];
77
78        BASE64_STANDARD.encode_string(trimmed_payload, &mut buffer);
79        buffer
80    }
81}