1use wowlab_common::node_http::{NodeRegistrationAccessRule, NodeRegistrationRequest};
2use wowlab_parsers::AccessRule;
3
4use crate::{
5 config::NodeConfig,
6 sentinel::{SentinelClient, SentinelError},
7};
8
9const TOKEN_PREFIX: &str = "wlab_claim_";
10const TOKEN_LEN: usize = 43;
11const DEFAULT_CORE_FALLBACK: i32 = 4;
12
13#[derive(Debug, thiserror::Error)]
15#[error("{kind}")]
16pub struct ClaimTokenError {
17 kind: ClaimTokenErrorKind,
18}
19
20#[derive(Debug, thiserror::Error)]
21enum ClaimTokenErrorKind {
22 #[error("")]
23 Empty,
24 #[error("Token must start with '{TOKEN_PREFIX}'")]
25 WrongPrefix,
26 #[error("Token must be {TOKEN_LEN} characters (currently {actual})")]
27 WrongLength { actual: usize },
28}
29
30impl ClaimTokenError {
31 const fn new(kind: ClaimTokenErrorKind) -> Self {
32 Self { kind }
33 }
34
35 #[must_use]
36 pub const fn is_empty(&self) -> bool {
37 matches!(self.kind, ClaimTokenErrorKind::Empty)
38 }
39}
40
41pub fn validate_token(token: &str) -> Result<(), ClaimTokenError> {
47 if token.is_empty() {
48 return Err(ClaimTokenError::new(ClaimTokenErrorKind::Empty));
49 }
50
51 if !token.starts_with(TOKEN_PREFIX) {
52 return Err(ClaimTokenError::new(ClaimTokenErrorKind::WrongPrefix));
53 }
54
55 if token.len() != TOKEN_LEN {
56 return Err(ClaimTokenError::new(ClaimTokenErrorKind::WrongLength {
57 actual: token.len(),
58 }));
59 }
60
61 Ok(())
62}
63
64pub(crate) fn default_name() -> String {
65 hostname::get()
66 .ok()
67 .and_then(|h| h.into_string().ok())
68 .unwrap_or_else(|| "WoW Lab Node".to_string())
69}
70
71pub(crate) fn total_cores() -> i32 {
72 i32::try_from(wowlab_common::sys::logical_cores()).unwrap_or(DEFAULT_CORE_FALLBACK)
73}
74
75pub(crate) fn default_enabled_cores() -> i32 {
76 i32::try_from(wowlab_common::sys::optimal_concurrency()).unwrap_or(DEFAULT_CORE_FALLBACK)
77}
78
79pub(crate) fn platform() -> String {
80 let os = std::env::consts::OS;
81 let arch = std::env::consts::ARCH;
82
83 format!("{os}-{arch}")
84}
85
86pub(crate) async fn register(
87 client: &SentinelClient,
88 config: &NodeConfig,
89 token_claim: &str,
90) -> Result<(), SentinelError> {
91 let platform = platform();
92 let version = env!("CARGO_PKG_VERSION");
93 let access_rules = config
94 .access
95 .rules
96 .iter()
97 .map(|rule| match rule {
98 AccessRule::Public => NodeRegistrationAccessRule {
99 access_type: "public".to_string(),
100 target_id: None,
101 },
102 AccessRule::Discord(id) => NodeRegistrationAccessRule {
103 access_type: "discord".to_string(),
104 target_id: Some(id.clone()),
105 },
106 AccessRule::Friends(id) => NodeRegistrationAccessRule {
107 access_type: "friends".to_string(),
108 target_id: Some(id.clone()),
109 },
110 })
111 .collect();
112
113 client
114 .register_node(&NodeRegistrationRequest {
115 token_claim: token_claim.to_string(),
116 hostname: Some(config.name.clone()),
117 total_cores: Some(config.total_cores),
118 enabled_cores: Some(config.enabled_cores),
119 platform: Some(platform),
120 version: Some(version.to_string()),
121 access_rules,
122 })
123 .await?;
124 tracing::info!(name = %config.name, "Registered node");
125
126 Ok(())
127}