use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
pub(crate) fn base64_encode(data: &[u8]) -> String {
BASE64.encode(data)
}
pub(crate) fn base64_decode(data: &str) -> std::result::Result<Vec<u8>, String> {
if data.len() > crate::group_mesh::MAX_BASE64_PAYLOAD_SIZE {
return Err(format!(
"payload too large: {} encoded bytes exceeds {} limit",
data.len(),
crate::group_mesh::MAX_BASE64_PAYLOAD_SIZE
));
}
BASE64.decode(data).map_err(|e| e.to_string())
}
macro_rules! define_internal_prefixes {
( $( $(#[$attr:meta])* $name:ident = $value:expr ),+ $(,)? ) => {
pub(crate) mod internal_prefixes {
$( $(#[$attr])* pub const $name: &str = $value; )+
}
pub(crate) const INTERNAL_PREFIXES: &[&str] = &[
$( internal_prefixes::$name, )+
offline_protocol_services::SVC_MESSAGE_PREFIX,
offline_protocol_services::SVC_DISCOVER_QUERY,
offline_protocol_services::SVC_DISCOVER_RESPONSE,
offline_protocol_services::SVC_REQUEST,
offline_protocol_services::SVC_RESPONSE,
];
};
}
define_internal_prefixes! {
KEY_PACKAGE = offline_protocol_sealed::prefixes::KEY_PACKAGE,
WELCOME = offline_protocol_sealed::prefixes::WELCOME,
ENCRYPTED = offline_protocol_sealed::prefixes::ENCRYPTED,
SESSION_CONFIRM_PROBE = offline_protocol_sealed::prefixes::SESSION_CONFIRM_PROBE,
SESSION_CONFIRM_ACK = offline_protocol_sealed::prefixes::SESSION_CONFIRM_ACK,
SESSION_CONFIRM_ENCRYPTED = offline_protocol_sealed::prefixes::SESSION_CONFIRM_ENCRYPTED,
RICH_V1 = "__RICH_V1__",
DATA_V1 = "__DATA_V1__",
CONN_REQUEST = "__CONN_REQ__",
CONN_ACCEPT = "__CONN_ACC__",
CONN_REJECT = "__CONN_REJ__",
CONN_CANCEL = "__CONN_CAN__",
GROUP_CREATED = "__GROUP_CREATED__",
GROUP_MSG = "__GROUP_MSG__",
GROUP_MEMBER_ADDED = "__GROUP_MEMBER_ADDED__",
GROUP_MEMBER_REMOVED = "__GROUP_MEMBER_REMOVED__",
GROUP_INFO = "__GROUP_INFO__",
USER_GROUPS = "__USER_GROUPS__",
GROUP_ERROR = "__GROUP_ERROR__",
GROUP_MLS_MSG = "__GRP_MLS_MSG__",
GROUP_MLS_WELCOME = "__GRP_MLS_WELCOME__",
GROUP_MLS_COMMIT = "__GRP_MLS_COMMIT__",
GROUP_MLS_LEAVE = "__GRP_MLS_LEAVE__",
GROUP_RELAY_REGISTER = "__GRP_RELAY_REG__",
GROUP_RELAY_BROADCAST = "__GRP_RELAY_BCAST__",
GROUP_ROLE_CHANGE = "__GRP_ROLE_CHG__",
GROUP_RENAME = "__GRP_RENAME__",
PRESENCE = "__PRESENCE__",
TYPING_INDICATOR = "__TYPING__",
READ_RECEIPT = "__READ_RECEIPT__",
}
pub(crate) const DATA_PLANE_PREFIXES: &[&str] =
&[internal_prefixes::ENCRYPTED, internal_prefixes::GROUP_MSG];
pub(crate) const RELAY_ANSWER_PREFIXES: &[&str] = &[
internal_prefixes::GROUP_CREATED,
internal_prefixes::GROUP_MEMBER_ADDED,
internal_prefixes::GROUP_MEMBER_REMOVED,
internal_prefixes::GROUP_INFO,
internal_prefixes::USER_GROUPS,
internal_prefixes::GROUP_ERROR,
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn relay_answer_prefixes_are_pinned() {
assert_eq!(
RELAY_ANSWER_PREFIXES,
[
"__GROUP_CREATED__",
"__GROUP_MEMBER_ADDED__",
"__GROUP_MEMBER_REMOVED__",
"__GROUP_INFO__",
"__USER_GROUPS__",
"__GROUP_ERROR__",
],
"the relay-answer exemption list changed — update RelayAnswerPrefixes.swift \
and RelayAnswerPrefixes.kt to match, or the bridges and the gate will \
disagree silently"
);
}
#[test]
fn the_two_exemption_lists_do_not_overlap() {
for relay in RELAY_ANSWER_PREFIXES {
assert!(
!DATA_PLANE_PREFIXES.contains(relay),
"'{}' is exempt twice, by two different rules",
relay
);
}
}
#[test]
fn every_exempt_prefix_is_an_internal_prefix() {
for relay in RELAY_ANSWER_PREFIXES {
assert!(
INTERNAL_PREFIXES.contains(relay),
"'{}' is not an internal prefix, so exempting it does nothing",
relay
);
}
}
}