/build/source/nativelink-util/src/common.rs
Line | Count | Source |
1 | | // Copyright 2024 The NativeLink Authors. All rights reserved. |
2 | | // |
3 | | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // See LICENSE file for details |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use core::cmp::{Eq, Ordering}; |
16 | | use core::hash::{BuildHasher, Hash}; |
17 | | use core::ops::{Deref, DerefMut}; |
18 | | use std::collections::HashMap; |
19 | | use std::fmt; |
20 | | use std::io::{Cursor, Write}; |
21 | | |
22 | | use bytes::{Buf, BufMut, Bytes, BytesMut}; |
23 | | use nativelink_error::{Error, ResultExt, make_input_err}; |
24 | | use nativelink_metric::{ |
25 | | MetricFieldData, MetricKind, MetricPublishKnownKindData, MetricsComponent, |
26 | | }; |
27 | | use nativelink_proto::build::bazel::remote::execution::v2::Digest; |
28 | | use prost::Message; |
29 | | use serde::de::Visitor; |
30 | | use serde::ser::Error as _; |
31 | | use serde::{Deserialize, Deserializer, Serialize, Serializer}; |
32 | | use tracing::error; |
33 | | |
34 | | pub use crate::fs; |
35 | | |
36 | | #[derive(Default, Clone, Copy, Eq, PartialEq, Hash)] |
37 | | #[repr(C)] |
38 | | pub struct DigestInfo { |
39 | | /// Raw hash in packed form. |
40 | | packed_hash: PackedHash, |
41 | | |
42 | | /// Possibly the size of the digest in bytes. |
43 | | size_bytes: u64, |
44 | | } |
45 | | |
46 | | impl MetricsComponent for DigestInfo { |
47 | 0 | fn publish( |
48 | 0 | &self, |
49 | 0 | _kind: MetricKind, |
50 | 0 | field_metadata: MetricFieldData, |
51 | 0 | ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> { |
52 | 0 | format!("{self}").publish(MetricKind::String, field_metadata) |
53 | 0 | } |
54 | | } |
55 | | |
56 | | impl DigestInfo { |
57 | 5.42k | pub const fn new(packed_hash: [u8; 32], size_bytes: u64) -> Self { |
58 | 5.42k | Self { |
59 | 5.42k | size_bytes, |
60 | 5.42k | packed_hash: PackedHash(packed_hash), |
61 | 5.42k | } |
62 | 5.42k | } |
63 | | |
64 | 5.45k | pub fn try_new<T>(hash: &str, size_bytes: T) -> Result<Self, Error> |
65 | 5.45k | where |
66 | 5.45k | T: TryInto<u64> + fmt::Display + Copy, |
67 | | { |
68 | 5.44k | let packed_hash = |
69 | 5.45k | PackedHash::from_hex(hash).err_tip(|| format!("Invalid sha256 hash: {hash}"9 ))?9 ; |
70 | 5.44k | let size_bytes = size_bytes |
71 | 5.44k | .try_into() |
72 | 5.44k | .map_err(|_| make_input_err!("Could not convert {} into u64", size_bytes))?0 ; |
73 | | // The proto `Digest` takes an i64, so to keep compatibility |
74 | | // we only allow sizes that can fit into an i64. |
75 | 5.44k | if size_bytes > i64::MAX as u64 { Branch (75:12): [True: 0, False: 3]
Branch (75:12): [True: 0, False: 166]
Branch (75:12): [True: 0, False: 21]
Branch (75:12): [True: 0, False: 4]
Branch (75:12): [True: 0, False: 2]
Branch (75:12): [True: 0, False: 4.58k]
Branch (75:12): [Folded - Ignored]
Branch (75:12): [True: 0, False: 1]
Branch (75:12): [True: 0, False: 6]
Branch (75:12): [True: 0, False: 8]
Branch (75:12): [True: 0, False: 1]
Branch (75:12): [True: 0, False: 6]
Branch (75:12): [True: 2, False: 5]
Branch (75:12): [True: 0, False: 6]
Branch (75:12): [True: 0, False: 9]
Branch (75:12): [True: 0, False: 58]
Branch (75:12): [True: 0, False: 5]
Branch (75:12): [True: 0, False: 1]
Branch (75:12): [True: 0, False: 11]
Branch (75:12): [True: 0, False: 18]
Branch (75:12): [True: 0, False: 10]
Branch (75:12): [True: 0, False: 2]
Branch (75:12): [True: 0, False: 405]
Branch (75:12): [True: 0, False: 1]
Branch (75:12): [True: 0, False: 2]
Branch (75:12): [True: 0, False: 0]
Branch (75:12): [True: 0, False: 0]
Branch (75:12): [True: 0, False: 2]
Branch (75:12): [Folded - Ignored]
Branch (75:12): [True: 0, False: 1]
Branch (75:12): [True: 0, False: 1]
Branch (75:12): [True: 0, False: 7]
Branch (75:12): [True: 0, False: 0]
Branch (75:12): [True: 0, False: 3]
Branch (75:12): [True: 0, False: 2]
Branch (75:12): [True: 0, False: 3]
Branch (75:12): [True: 0, False: 1]
Branch (75:12): [True: 0, False: 6]
Branch (75:12): [True: 0, False: 6]
Branch (75:12): [True: 0, False: 27]
Branch (75:12): [True: 0, False: 3]
Branch (75:12): [True: 0, False: 3]
Branch (75:12): [True: 0, False: 4]
Branch (75:12): [True: 0, False: 3]
Branch (75:12): [True: 0, False: 13]
Branch (75:12): [True: 0, False: 12]
Branch (75:12): [True: 0, False: 10]
|
76 | 2 | return Err(make_input_err!( |
77 | 2 | "Size bytes is too large: {} - max: {}", |
78 | 2 | size_bytes, |
79 | 2 | i64::MAX |
80 | 2 | )); |
81 | 5.44k | } |
82 | 5.44k | Ok(Self { |
83 | 5.44k | packed_hash, |
84 | 5.44k | size_bytes, |
85 | 5.44k | }) |
86 | 5.45k | } |
87 | | |
88 | 29 | pub const fn zero_digest() -> Self { |
89 | 29 | Self { |
90 | 29 | size_bytes: 0, |
91 | 29 | packed_hash: PackedHash::new(), |
92 | 29 | } |
93 | 29 | } |
94 | | |
95 | 40.3k | pub const fn packed_hash(&self) -> &PackedHash { |
96 | 40.3k | &self.packed_hash |
97 | 40.3k | } |
98 | | |
99 | 143 | pub const fn set_packed_hash(&mut self, packed_hash: [u8; 32]) { |
100 | 143 | self.packed_hash = PackedHash(packed_hash); |
101 | 143 | } |
102 | | |
103 | 16.2k | pub const fn size_bytes(&self) -> u64 { |
104 | 16.2k | self.size_bytes |
105 | 16.2k | } |
106 | | |
107 | | /// Returns a struct that can turn the `DigestInfo` into a string. |
108 | 909 | const fn stringifier(&self) -> DigestStackStringifier<'_> { |
109 | 909 | DigestStackStringifier::new(self) |
110 | 909 | } |
111 | | } |
112 | | |
113 | | /// Counts the number of digits a number needs if it were to be |
114 | | /// converted to a string. |
115 | 0 | const fn count_digits(mut num: u64) -> usize { |
116 | 0 | let mut count = 0; |
117 | 0 | while num != 0 { Branch (117:11): [Folded - Ignored]
Branch (117:11): [Folded - Ignored]
|
118 | 0 | count += 1; |
119 | 0 | num /= 10; |
120 | 0 | } |
121 | 0 | count |
122 | 0 | } |
123 | | |
124 | | /// An optimized version of a function that can convert a `DigestInfo` |
125 | | /// into a str on the stack. |
126 | | struct DigestStackStringifier<'a> { |
127 | | digest: &'a DigestInfo, |
128 | | /// Buffer that can hold the string representation of the `DigestInfo`. |
129 | | /// - Hex is '2 * sizeof(PackedHash)'. |
130 | | /// - Digits can be at most `count_digits(u64::MAX)`. |
131 | | /// - We also have a hyphen separator. |
132 | | buf: [u8; size_of::<PackedHash>() * 2 + count_digits(u64::MAX) + 1], |
133 | | } |
134 | | |
135 | | impl<'a> DigestStackStringifier<'a> { |
136 | 909 | const fn new(digest: &'a DigestInfo) -> Self { |
137 | 909 | DigestStackStringifier { |
138 | 909 | digest, |
139 | 909 | buf: [b'-'; size_of::<PackedHash>() * 2 + count_digits(u64::MAX) + 1], |
140 | 909 | } |
141 | 909 | } |
142 | | |
143 | 909 | fn as_str(&mut self) -> Result<&str, Error> { |
144 | | // Populate the buffer and return the amount of bytes written |
145 | | // to the buffer. |
146 | 909 | let len = { |
147 | 909 | let mut cursor = Cursor::new(&mut self.buf[..]); |
148 | 909 | let hex = self.digest.packed_hash.to_hex().map_err(|e| {0 |
149 | 0 | make_input_err!( |
150 | | "Could not convert PackedHash to hex - {e:?} - {:?}", |
151 | | self.digest |
152 | | ) |
153 | 0 | })?; |
154 | 909 | cursor |
155 | 909 | .write_all(&hex) |
156 | 909 | .err_tip(|| format!("Could not write hex to buffer - {hex:?} - {hex:?}"0 ,))?0 ; |
157 | | // Note: We already have a hyphen at this point because we |
158 | | // initialized the buffer with hyphens. |
159 | 909 | cursor.advance(1); |
160 | 909 | cursor |
161 | 909 | .write_fmt(format_args!("{}", self.digest.size_bytes())) |
162 | 909 | .err_tip(|| format!("Could not write size_bytes to buffer - {hex:?}"0 ,))?0 ; |
163 | 909 | cursor |
164 | 909 | .position() |
165 | 909 | .try_into() |
166 | 909 | .map_err(|e| make_input_err!("Cursor position exceeds usize bounds: {e}"))?0 |
167 | | }; |
168 | | // Convert the buffer into utf8 string. |
169 | 909 | core::str::from_utf8(&self.buf[..len]).map_err(|e| {0 |
170 | 0 | make_input_err!( |
171 | | "Could not convert [u8] to string - {} - {:?} - {:?}", |
172 | | self.digest, |
173 | | self.buf, |
174 | | e, |
175 | | ) |
176 | 0 | }) |
177 | 909 | } |
178 | | } |
179 | | |
180 | | /// Custom serializer for `DigestInfo` because the default Serializer |
181 | | /// would try to encode the data as a byte array, but we use {hex}-{size}. |
182 | | impl Serialize for DigestInfo { |
183 | 181 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
184 | 181 | where |
185 | 181 | S: Serializer, |
186 | | { |
187 | 181 | let mut stringifier = self.stringifier(); |
188 | 181 | serializer.serialize_str( |
189 | 181 | stringifier |
190 | 181 | .as_str() |
191 | 181 | .err_tip(|| "During serialization of DigestInfo") |
192 | 181 | .map_err(S::Error::custom)?0 , |
193 | | ) |
194 | 181 | } |
195 | | } |
196 | | |
197 | | /// Custom deserializer for `DigestInfo` because the default Deserializer |
198 | | /// would try to decode the data as a byte array, but we use {hex}-{size}. |
199 | | impl<'de> Deserialize<'de> for DigestInfo { |
200 | 4.76k | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
201 | 4.76k | where |
202 | 4.76k | D: Deserializer<'de>, |
203 | | { |
204 | | struct DigestInfoVisitor; |
205 | | impl Visitor<'_> for DigestInfoVisitor { |
206 | | type Value = DigestInfo; |
207 | | |
208 | 0 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
209 | 0 | formatter.write_str("a string representing a DigestInfo") |
210 | 0 | } |
211 | | |
212 | 4.76k | fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> |
213 | 4.76k | where |
214 | 4.76k | E: serde::de::Error, |
215 | | { |
216 | 4.76k | let Some((hash, size)) = s.split_once('-') else { Branch (216:21): [True: 166, False: 0]
Branch (216:21): [True: 0, False: 0]
Branch (216:21): [True: 4.58k, False: 0]
Branch (216:21): [Folded - Ignored]
Branch (216:21): [True: 6, False: 0]
Branch (216:21): [True: 3, False: 0]
Branch (216:21): [Folded - Ignored]
Branch (216:21): [True: 0, False: 0]
|
217 | 0 | return Err(E::custom( |
218 | 0 | "Invalid DigestInfo format, expected '-' separator", |
219 | 0 | )); |
220 | | }; |
221 | 4.76k | let size_bytes = size |
222 | 4.76k | .parse::<u64>() |
223 | 4.76k | .map_err(|e| E::custom0 (format!0 ("Could not parse size_bytes: {e:?}"0 )))?0 ; |
224 | 4.76k | DigestInfo::try_new(hash, size_bytes) |
225 | 4.76k | .map_err(|e| E::custom1 (format!1 ("Could not create DigestInfo: {e:?}"1 ))) |
226 | 4.76k | } |
227 | | } |
228 | 4.76k | deserializer.deserialize_str(DigestInfoVisitor) |
229 | 4.76k | } |
230 | | } |
231 | | |
232 | | impl fmt::Display for DigestInfo { |
233 | 586 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
234 | 586 | let mut stringifier = self.stringifier(); |
235 | 586 | f.write_str( |
236 | 586 | stringifier |
237 | 586 | .as_str() |
238 | 586 | .err_tip(|| "During serialization of DigestInfo") |
239 | 586 | .map_err(|e| {0 |
240 | 0 | error!("Could not convert DigestInfo to string - {e:?}"); |
241 | 0 | fmt::Error |
242 | 0 | })?, |
243 | | ) |
244 | 586 | } |
245 | | } |
246 | | |
247 | | impl fmt::Debug for DigestInfo { |
248 | 142 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
249 | 142 | let mut stringifier = self.stringifier(); |
250 | 142 | match stringifier.as_str() { |
251 | 142 | Ok(s) => f.debug_tuple("DigestInfo").field(&s).finish(), |
252 | 0 | Err(e) => { |
253 | 0 | error!("Could not convert DigestInfo to string - {e:?}"); |
254 | 0 | Err(fmt::Error) |
255 | | } |
256 | | } |
257 | 142 | } |
258 | | } |
259 | | |
260 | | impl Ord for DigestInfo { |
261 | 0 | fn cmp(&self, other: &Self) -> Ordering { |
262 | 0 | self.packed_hash |
263 | 0 | .cmp(&other.packed_hash) |
264 | 0 | .then_with(|| self.size_bytes.cmp(&other.size_bytes)) |
265 | 0 | } |
266 | | } |
267 | | |
268 | | impl PartialOrd for DigestInfo { |
269 | 0 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
270 | 0 | Some(self.cmp(other)) |
271 | 0 | } |
272 | | } |
273 | | |
274 | | impl TryFrom<Digest> for DigestInfo { |
275 | | type Error = Error; |
276 | | |
277 | 205 | fn try_from(digest: Digest) -> Result<Self, Self::Error> { |
278 | 205 | let packed_hash204 = PackedHash::from_hex(&digest.hash) |
279 | 205 | .err_tip(|| format!("Invalid sha256 hash: {}"1 , digest.hash))?1 ; |
280 | 204 | let size_bytes = digest |
281 | 204 | .size_bytes |
282 | 204 | .try_into() |
283 | 204 | .map_err(|_| make_input_err!("Could not convert {} into u64", digest.size_bytes))?0 ; |
284 | 204 | Ok(Self { |
285 | 204 | packed_hash, |
286 | 204 | size_bytes, |
287 | 204 | }) |
288 | 205 | } |
289 | | } |
290 | | |
291 | | impl TryFrom<&Digest> for DigestInfo { |
292 | | type Error = Error; |
293 | | |
294 | 0 | fn try_from(digest: &Digest) -> Result<Self, Self::Error> { |
295 | 0 | let packed_hash = PackedHash::from_hex(&digest.hash) |
296 | 0 | .err_tip(|| format!("Invalid sha256 hash: {}", digest.hash))?; |
297 | 0 | let size_bytes = digest |
298 | 0 | .size_bytes |
299 | 0 | .try_into() |
300 | 0 | .map_err(|_| make_input_err!("Could not convert {} into u64", digest.size_bytes))?; |
301 | 0 | Ok(Self { |
302 | 0 | packed_hash, |
303 | 0 | size_bytes, |
304 | 0 | }) |
305 | 0 | } |
306 | | } |
307 | | |
308 | | impl From<DigestInfo> for Digest { |
309 | 234 | fn from(val: DigestInfo) -> Self { |
310 | | Self { |
311 | 234 | hash: val.packed_hash.to_string(), |
312 | 234 | size_bytes: val.size_bytes.try_into().unwrap_or_else(|e| {0 |
313 | 0 | error!("Could not convert {} into u64 - {e:?}", val.size_bytes); |
314 | | // This is a placeholder value that can help a user identify |
315 | | // that the conversion failed. |
316 | 0 | -255 |
317 | 0 | }), |
318 | | } |
319 | 234 | } |
320 | | } |
321 | | |
322 | | impl From<&DigestInfo> for Digest { |
323 | 10 | fn from(val: &DigestInfo) -> Self { |
324 | | Self { |
325 | 10 | hash: val.packed_hash.to_string(), |
326 | 10 | size_bytes: val.size_bytes.try_into().unwrap_or_else(|e| {0 |
327 | 0 | error!("Could not convert {} into u64 - {e:?}", val.size_bytes); |
328 | | // This is a placeholder value that can help a user identify |
329 | | // that the conversion failed. |
330 | 0 | -255 |
331 | 0 | }), |
332 | | } |
333 | 10 | } |
334 | | } |
335 | | |
336 | | #[derive( |
337 | | Debug, Serialize, Deserialize, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, |
338 | | )] |
339 | | pub struct PackedHash([u8; 32]); |
340 | | |
341 | | const SIZE_OF_PACKED_HASH: usize = 32; |
342 | | impl PackedHash { |
343 | 0 | const fn new() -> Self { |
344 | 0 | Self([0; SIZE_OF_PACKED_HASH]) |
345 | 0 | } |
346 | | |
347 | 5.66k | fn from_hex(hash: &str) -> Result<Self, Error> { |
348 | 5.66k | let mut packed_hash = [0u8; 32]; |
349 | 5.66k | hex::decode_to_slice(hash, &mut packed_hash) |
350 | 5.66k | .map_err(|e| make_input_err!("Invalid sha256 hash: {hash} - {e:?}"))?10 ; |
351 | 5.65k | Ok(Self(packed_hash)) |
352 | 5.66k | } |
353 | | |
354 | | /// Converts the packed hash into a hex string. |
355 | | #[inline] |
356 | 1.18k | fn to_hex(self) -> Result<[u8; SIZE_OF_PACKED_HASH * 2], fmt::Error> { |
357 | 1.18k | let mut hash = [0u8; SIZE_OF_PACKED_HASH * 2]; |
358 | 1.18k | hex::encode_to_slice(self.0, &mut hash).map_err(|e| {0 |
359 | 0 | error!("Could not convert PackedHash to hex - {e:?} - {:?}", self.0); |
360 | 0 | fmt::Error |
361 | 0 | })?; |
362 | 1.18k | Ok(hash) |
363 | 1.18k | } |
364 | | } |
365 | | |
366 | | impl fmt::Display for PackedHash { |
367 | 276 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
368 | 276 | let hash = self.to_hex()?0 ; |
369 | 276 | match core::str::from_utf8(&hash) { |
370 | 276 | Ok(hash) => f.write_str(hash)?0 , |
371 | 0 | Err(_) => f.write_str(&format!("Could not convert hash to utf8 {:?}", self.0))?, |
372 | | } |
373 | 276 | Ok(()) |
374 | 276 | } |
375 | | } |
376 | | |
377 | | impl Deref for PackedHash { |
378 | | type Target = [u8; 32]; |
379 | | |
380 | 40.2k | fn deref(&self) -> &Self::Target { |
381 | 40.2k | &self.0 |
382 | 40.2k | } |
383 | | } |
384 | | |
385 | | impl DerefMut for PackedHash { |
386 | 143 | fn deref_mut(&mut self) -> &mut Self::Target { |
387 | 143 | &mut self.0 |
388 | 143 | } |
389 | | } |
390 | | |
391 | | // Simple utility trait that makes it easier to apply `.try_map` to Vec. |
392 | | // This will convert one vector into another vector with a different type. |
393 | | pub trait VecExt<T> { |
394 | | fn try_map<F, U>(self, f: F) -> Result<Vec<U>, Error> |
395 | | where |
396 | | Self: Sized, |
397 | | F: (Fn(T) -> Result<U, Error>) + Sized; |
398 | | } |
399 | | |
400 | | impl<T> VecExt<T> for Vec<T> { |
401 | 12 | fn try_map<F, U>(self, f: F) -> Result<Vec<U>, Error> |
402 | 12 | where |
403 | 12 | Self: Sized, |
404 | 12 | F: (Fn(T) -> Result<U, Error>) + Sized, |
405 | | { |
406 | 12 | let mut output = Vec::with_capacity(self.len()); |
407 | 18 | for item6 in self { |
408 | 6 | output.push((f)(item)?0 ); |
409 | | } |
410 | 12 | Ok(output) |
411 | 12 | } |
412 | | } |
413 | | |
414 | | // Simple utility trait that makes it easier to apply `.try_map` to HashMap. |
415 | | // This will convert one HashMap into another keeping the key the same, but |
416 | | // different value type. |
417 | | pub trait HashMapExt<K: Eq + Hash, T, S: BuildHasher> { |
418 | | fn try_map<F, U>(self, f: F) -> Result<HashMap<K, U, S>, Error> |
419 | | where |
420 | | Self: Sized, |
421 | | F: (Fn(T) -> Result<U, Error>) + Sized; |
422 | | } |
423 | | |
424 | | impl<K: Eq + Hash, T, S: BuildHasher + Clone> HashMapExt<K, T, S> for HashMap<K, T, S> { |
425 | 3 | fn try_map<F, U>(self, f: F) -> Result<HashMap<K, U, S>, Error> |
426 | 3 | where |
427 | 3 | Self: Sized, |
428 | 3 | F: (Fn(T) -> Result<U, Error>) + Sized, |
429 | | { |
430 | 3 | let mut output = HashMap::with_capacity_and_hasher(self.len(), (*self.hasher()).clone()); |
431 | 5 | for (k2 , v2 ) in self { |
432 | 2 | output.insert(k, (f)(v)?0 ); |
433 | | } |
434 | 3 | Ok(output) |
435 | 3 | } |
436 | | } |
437 | | |
438 | | // Utility to encode our proto into GRPC stream format. |
439 | 38 | pub fn encode_stream_proto<T: Message>(proto: &T) -> Result<Bytes, Box<dyn core::error::Error>> { |
440 | | // See below comment on spec. |
441 | | use core::mem::size_of; |
442 | | const PREFIX_BYTES: usize = size_of::<u8>() + size_of::<u32>(); |
443 | | |
444 | 38 | let mut buf = BytesMut::new(); |
445 | | |
446 | 228 | for _ in 0..PREFIX_BYTES { |
447 | 190 | // Advance our buffer first. |
448 | 190 | // We will backfill it once we know the size of the message. |
449 | 190 | buf.put_u8(0); |
450 | 190 | } |
451 | 38 | proto.encode(&mut buf)?0 ; |
452 | 38 | let len = buf.len() - PREFIX_BYTES; |
453 | | { |
454 | 38 | let mut buf = &mut buf[0..PREFIX_BYTES]; |
455 | | // See: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#:~:text=Compressed-Flag |
456 | | // for more details on spec. |
457 | | // Compressed-Flag -> 0 / 1 # encoded as 1 byte unsigned integer. |
458 | 38 | buf.put_u8(0); |
459 | | // Message-Length -> {length of Message} # encoded as 4 byte unsigned integer (big endian). |
460 | 38 | buf.put_u32(u32::try_from(len)?0 ); |
461 | | // Message -> *{binary octet}. |
462 | | } |
463 | | |
464 | 38 | Ok(buf.freeze()) |
465 | 38 | } |
466 | | |
467 | | /// Small utility to reseed the global RNG. |
468 | | /// Done this way because we use it in a macro |
469 | | /// and macro's can't load external crates. |
470 | | #[inline] |
471 | 403 | pub fn reseed_rng_for_test() -> Result<(), Error> { |
472 | 403 | rand::rng() |
473 | 403 | .reseed() |
474 | 403 | .map_err(|e| make_input_err!("Could not reseed RNG - {e:?}")) |
475 | 403 | } |