1use a7n_proto::{
4 MachineId, Pin, ProtoError, limits,
5 v1::{DeskMember, DeskRevocation},
6};
7
8const MEMBER_DOMAIN: &[u8] = b"a7n desk member v1\0";
9const REVOCATION_DOMAIN: &[u8] = b"a7n desk revoke v1\0";
10pub(super) const SIGNATURE_LEN: usize = 64;
11
12#[derive(Debug, thiserror::Error)]
14pub enum DeskError {
15 #[error(transparent)]
16 Proto(#[from] ProtoError),
17 #[error(
18 "peer name must contain 1..={} Unicode code points",
19 limits::PEER_NAME_MAX
20 )]
21 BadName,
22 #[error("desk signature must contain {SIGNATURE_LEN} bytes")]
23 BadSignature,
24 #[error("desk record capacity exceeded")]
25 Capacity,
26}
27
28fn validate_name(name: &str) -> Result<(), DeskError> {
29 if name.is_empty()
30 || name.chars().take(limits::PEER_NAME_MAX + 1).count() > limits::PEER_NAME_MAX
31 {
32 return Err(DeskError::BadName);
33 }
34 Ok(())
35}
36
37pub fn member_signing_input(
42 id: MachineId,
43 pin: Pin,
44 name: &str,
45 introduced_by: MachineId,
46 added_at: u64,
47) -> Result<Vec<u8>, DeskError> {
48 validate_name(name)?;
49 let name_len = u16::try_from(name.len()).map_err(|_| DeskError::BadName)?;
50 let mut input = MEMBER_DOMAIN.to_vec();
51 input.extend_from_slice(id.as_bytes());
52 input.extend_from_slice(pin.as_bytes());
53 input.extend_from_slice(&name_len.to_be_bytes());
54 input.extend_from_slice(name.as_bytes());
55 input.extend_from_slice(introduced_by.as_bytes());
56 input.extend_from_slice(&added_at.to_be_bytes());
57 Ok(input)
58}
59
60#[must_use]
62pub fn revocation_signing_input(id: MachineId, revoked_by: MachineId, revoked_at: u64) -> Vec<u8> {
63 let mut input = REVOCATION_DOMAIN.to_vec();
64 input.extend_from_slice(id.as_bytes());
65 input.extend_from_slice(revoked_by.as_bytes());
66 input.extend_from_slice(&revoked_at.to_be_bytes());
67 input
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Member {
73 pub(super) id: MachineId,
74 pub(super) pin: Pin,
75 pub(super) name: String,
76 pub(super) introduced_by: MachineId,
77 pub(super) added_at: u64,
78 pub(super) signature: [u8; SIGNATURE_LEN],
79}
80
81impl Member {
82 #[must_use]
83 pub fn id(&self) -> MachineId {
84 self.id
85 }
86 #[must_use]
87 pub fn pin(&self) -> Pin {
88 self.pin
89 }
90 #[must_use]
91 pub fn name(&self) -> &str {
92 &self.name
93 }
94 #[must_use]
95 pub fn introduced_by(&self) -> MachineId {
96 self.introduced_by
97 }
98 #[must_use]
99 pub fn added_at(&self) -> u64 {
100 self.added_at
101 }
102 #[must_use]
103 pub fn signature(&self) -> &[u8] {
104 &self.signature
105 }
106
107 pub(super) fn signing_input(&self) -> Vec<u8> {
108 member_signing_input(
109 self.id,
110 self.pin,
111 &self.name,
112 self.introduced_by,
113 self.added_at,
114 )
115 .expect("Member has a checked name")
116 }
117}
118
119impl TryFrom<&DeskMember> for Member {
120 type Error = DeskError;
121
122 fn try_from(record: &DeskMember) -> Result<Self, Self::Error> {
123 validate_name(&record.name)?;
124 Ok(Self {
125 id: MachineId::try_from(record.id.as_slice())?,
126 pin: Pin::try_from(record.pin.as_slice())?,
127 name: record.name.clone(),
128 introduced_by: MachineId::try_from(record.introduced_by.as_slice())?,
129 added_at: record.added_at,
130 signature: record
131 .signature
132 .as_slice()
133 .try_into()
134 .map_err(|_| DeskError::BadSignature)?,
135 })
136 }
137}
138
139impl From<&Member> for DeskMember {
140 fn from(record: &Member) -> Self {
141 Self {
142 id: record.id.as_bytes().to_vec(),
143 pin: record.pin.as_bytes().to_vec(),
144 name: record.name.clone(),
145 introduced_by: record.introduced_by.as_bytes().to_vec(),
146 added_at: record.added_at,
147 signature: record.signature.to_vec(),
148 }
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct Revocation {
155 pub(super) id: MachineId,
156 pub(super) revoked_by: MachineId,
157 pub(super) revoked_at: u64,
158 pub(super) signature: [u8; SIGNATURE_LEN],
159}
160
161impl Revocation {
162 #[must_use]
163 pub fn id(&self) -> MachineId {
164 self.id
165 }
166 #[must_use]
167 pub fn revoked_by(&self) -> MachineId {
168 self.revoked_by
169 }
170 #[must_use]
171 pub fn revoked_at(&self) -> u64 {
172 self.revoked_at
173 }
174 #[must_use]
175 pub fn signature(&self) -> &[u8] {
176 &self.signature
177 }
178}
179
180impl TryFrom<&DeskRevocation> for Revocation {
181 type Error = DeskError;
182
183 fn try_from(record: &DeskRevocation) -> Result<Self, Self::Error> {
184 Ok(Self {
185 id: MachineId::try_from(record.id.as_slice())?,
186 revoked_by: MachineId::try_from(record.revoked_by.as_slice())?,
187 revoked_at: record.revoked_at,
188 signature: record
189 .signature
190 .as_slice()
191 .try_into()
192 .map_err(|_| DeskError::BadSignature)?,
193 })
194 }
195}
196
197impl From<&Revocation> for DeskRevocation {
198 fn from(record: &Revocation) -> Self {
199 Self {
200 id: record.id.as_bytes().to_vec(),
201 revoked_by: record.revoked_by.as_bytes().to_vec(),
202 revoked_at: record.revoked_at,
203 signature: record.signature.to_vec(),
204 }
205 }
206}