Skip to main content

brooks_lib/dcdn/
common.rs

1// brooks, Copyright 2026, Will Hawkins
2//
3// This file is part of brooks.
4
5// This file is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18use chrono::Utc;
19use http::Request;
20use tokio::runtime::Handle;
21
22use crate::{
23    cdni::imd::spec::TypedHostMetadata,
24    dcdn::{
25        error::{ImdInterpretError, ProxyError, ProxyResult},
26        imdi::interpret_interconnection_metadata,
27        interpret::{
28            Error::{self, AssertionFailure},
29            ProcessedRequestResponse,
30        },
31    },
32    environment::scope::{Scope, Scopes},
33    hmds::{HmdsConfiguration, query_hmds},
34    logging::{LogLevel, LogMsg, LogMsgs},
35    mel::{
36        MEL_USER_SET_VARIABLE_STRUCT_NAME,
37        interpreter::values::{TypedValue, mel_var_value_from_type},
38        types::{Type, builtin_function_types, minimal_core_variable_types},
39    },
40    prr,
41    verifying::imdv::verify_interconnection_metadata,
42};
43
44pub fn safe_brooks_integration_handle(
45    request: &Request<Vec<u8>>,
46    mel: Scopes<TypedValue>,
47    hmds_key: &str,
48    hmds_config: &mut HmdsConfiguration,
49    runtime: &Handle,
50    mut log: LogMsgs,
51) -> ProxyResult {
52    // First, try to find the query in the cache.
53    log = debug!(
54        log,
55        &format!("Looking for {hmds_key} in the host metadata cache.")
56    );
57
58    let found = match hmds_config.hmds_cache.get(hmds_key) {
59        Some((timeout, found)) => {
60            log = debug!(
61                log,
62                &format!(
63                    "Found {hmds_key} in the host metadata cache -- it is valid until {timeout}."
64                )
65            );
66            if Utc::now() > *timeout {
67                log = debug!(
68                    log,
69                    &format!("{hmds_key} in the host metadata cache timed out.")
70                );
71                hmds_config.hmds_cache.remove(hmds_key);
72                None
73            } else {
74                Some(found.clone())
75            }
76        }
77        None => None,
78    };
79
80    let (verified_md, updated_types_scopes) = if let Some(found) = found {
81        found
82    } else {
83        let res = match runtime.block_on(query_hmds(hmds_key, &hmds_config.hmds_server)) {
84            Ok(o) => o,
85            Err(e) => return Err((ProxyError::HmdsError(e.into()).into(), log)),
86        };
87        let (expiry, query_result) = match res {
88            Some((timeout, query_result)) => (timeout, query_result),
89            None => {
90                return Err((
91                    ProxyError::HmdsError(
92                        format!("Missing HMD configuration for {hmds_key}").into(),
93                    )
94                    .into(),
95                    log,
96                ));
97            }
98        };
99
100        let metadata = match serde_json::from_value::<TypedHostMetadata<()>>(query_result) {
101            Ok(o) => o,
102            Err(e) => {
103                return Err((ProxyError::HmdsError(e.into()).into(), log));
104            }
105        };
106
107        let types_scope = Scopes::<Type> {
108            scopes: vec![&minimal_core_variable_types() + &builtin_function_types()],
109        };
110
111        let (verified_md, updated_types_scope) =
112            match verify_interconnection_metadata(&metadata.value, types_scope) {
113                Ok(o) => o,
114                Err(e) => {
115                    return Err((ProxyError::HmdsError(e).into(), log));
116                }
117            };
118
119        log = debug!(
120            log,
121            &format!("Put {hmds_key} in the host metadata cache to expire at {expiry}.")
122        );
123        hmds_config.hmds_cache.insert(
124            hmds_key.to_string(),
125            (expiry, (verified_md.clone(), updated_types_scope.clone())),
126        );
127
128        (verified_md, updated_types_scope)
129    };
130
131    let processed_http_req = match TryInto::<ProcessedRequestResponse>::try_into(request) {
132        Ok(o) => o,
133        Err(e) => {
134            return Err((ProxyError::SetupError(e.into()).into(), log));
135        }
136    };
137
138    let processed_http_req = Box::new(processed_http_req);
139    let mel_scopes: Scopes<TypedValue> = mel.enter_scope(Into::<Scope<TypedValue>>::into(
140        &*processed_http_req as &dyn prr::Prr<Vec<u8>>,
141    ));
142
143    // Get the updated var struct (after processing any MI.SetVariables in the HMD), and
144    // build up a more complete type for its value during interpretation.
145    let var = match updated_types_scopes
146        .lookup(MEL_USER_SET_VARIABLE_STRUCT_NAME)
147        .ok_or(ProxyError::ImdiError(ImdInterpretError::RuntimeError(
148            Error::AssertionFailure(
149                format!(
150                    "Missing {} struct type after analyzing HMD",
151                    MEL_USER_SET_VARIABLE_STRUCT_NAME
152                )
153                .into(),
154            )
155            .into(),
156        ))) {
157        Ok(variable) => match variable {
158            Type::Struct(s) => s,
159            _ => {
160                return Err((
161                    ProxyError::ImdiError(ImdInterpretError::MetadataProcessingError(
162                        AssertionFailure(
163                            format!(
164                                "{} has wrong type after analyzing HMD",
165                                MEL_USER_SET_VARIABLE_STRUCT_NAME
166                            )
167                            .into(),
168                        )
169                        .into(),
170                    ))
171                    .into(),
172                    log,
173                ));
174            }
175        },
176        Err(e) => return Err((e.into(), log)),
177    };
178
179    log = debug!(
180        log,
181        &format!(
182            "Creating more-specific type for {} struct: {}",
183            MEL_USER_SET_VARIABLE_STRUCT_NAME, var
184        )
185    );
186    let mel_scopes = mel_scopes.insert(
187        MEL_USER_SET_VARIABLE_STRUCT_NAME,
188        mel_var_value_from_type(var),
189    );
190
191    interpret_interconnection_metadata(&verified_md, mel_scopes, processed_http_req, runtime, log)
192        .map_err(|(e, log)| (ProxyError::ImdiError(*e).into(), log))
193}