native_ossl/params.rs
1//! `OSSL_PARAM_BLD` builder and `Params<'a>` wrapper.
2//!
3//! `ParamBuilder` wraps `OSSL_PARAM_BLD` and provides typed push methods that
4//! map Rust types to OpenSSL parameter types. `build()` finalises the builder
5//! and returns an owned `Params` array.
6//!
7//! ## Lifetime
8//!
9//! `ParamBuilder<'a>` carries a lifetime `'a` that covers any borrowed byte
10//! slices passed via `push_octet_ptr` or `push_utf8_ptr`. Those calls store
11//! a pointer into the caller's buffer; the buffer must outlive the `Params`
12//! returned by `build()`. Calls that copy (`push_octet_slice`, `push_utf8_string`)
13//! have no such constraint.
14//!
15//! ## Zero-copy decision table
16//!
17//! | Method | C function | Copies? |
18//! |--------------------|---------------------------------|---------|
19//! | `push_octet_slice` | `OSSL_PARAM_BLD_push_octet_string` | Yes |
20//! | `push_octet_ptr` | `OSSL_PARAM_BLD_push_octet_ptr` | No |
21//! | `push_utf8_string` | `OSSL_PARAM_BLD_push_utf8_string` | Yes |
22//! | `push_utf8_ptr` | (manual OSSL_PARAM_BLD extension) | No |
23
24use crate::error::ErrorStack;
25use native_ossl_sys as sys;
26use std::ffi::CStr;
27use std::marker::PhantomData;
28use std::ptr;
29
30// ── Params — the built array ──────────────────────────────────────────────────
31
32/// An owned, finalized `OSSL_PARAM` array.
33///
34/// Created by `ParamBuilder::build()`. The lifetime `'a` covers any borrowed
35/// byte or string slices stored by pointer (zero-copy push calls).
36pub struct Params<'a> {
37 /// The heap-allocated array of `OSSL_PARAM` elements.
38 ptr: *mut sys::OSSL_PARAM,
39 /// Phantom to carry the borrow lifetime of any `push_octet_ptr` data.
40 _lifetime: PhantomData<&'a [u8]>,
41}
42
43impl Params<'_> {
44 /// Return a const pointer to the first `OSSL_PARAM` in the array.
45 ///
46 /// Pass this pointer to OpenSSL functions that take `const OSSL_PARAM[]`.
47 /// The pointer is valid for the lifetime of `self`.
48 #[must_use]
49 pub fn as_ptr(&self) -> *const sys::OSSL_PARAM {
50 self.ptr
51 }
52}
53
54impl Drop for Params<'_> {
55 fn drop(&mut self) {
56 unsafe { sys::OSSL_PARAM_free(self.ptr) };
57 }
58}
59
60// SAFETY: `OSSL_PARAM` arrays are read-only after construction.
61// The builder ensures all owned copies are heap-allocated by OpenSSL.
62unsafe impl Send for Params<'_> {}
63unsafe impl Sync for Params<'_> {}
64
65// ── ParamBuilder ──────────────────────────────────────────────────────────────
66
67/// Builder for `OSSL_PARAM` arrays.
68///
69/// ```ignore
70/// use native_ossl::params::ParamBuilder;
71///
72/// let params = ParamBuilder::new()
73/// .push_int(c"key_bits", 2048)?
74/// .push_utf8_ptr(c"pad-mode", c"oaep")?
75/// .build()?;
76/// ```
77pub struct ParamBuilder<'a> {
78 ptr: *mut sys::OSSL_PARAM_BLD,
79 /// BIGNUMs pushed via `push_bn` that must remain alive until `build()`.
80 ///
81 /// `OSSL_PARAM_BLD_push_BN` stores a *pointer* to the BIGNUM internally;
82 /// the actual copy into the `OSSL_PARAM` array happens only in
83 /// `OSSL_PARAM_BLD_to_param`. We must therefore keep each BIGNUM alive
84 /// until after `build()` completes.
85 bns: Vec<*mut sys::BIGNUM>,
86 _lifetime: PhantomData<&'a [u8]>,
87}
88
89// SAFETY: BIGNUM pointers are not shared; we control their lifetime.
90unsafe impl Send for ParamBuilder<'_> {}
91
92impl<'a> ParamBuilder<'a> {
93 /// Create a new empty builder.
94 ///
95 /// # Errors
96 ///
97 /// Returns `Err` if OpenSSL cannot allocate the builder.
98 pub fn new() -> Result<Self, ErrorStack> {
99 let ptr = unsafe { sys::OSSL_PARAM_BLD_new() };
100 if ptr.is_null() {
101 return Err(ErrorStack::drain());
102 }
103 Ok(ParamBuilder {
104 ptr,
105 bns: Vec::new(),
106 _lifetime: PhantomData,
107 })
108 }
109
110 /// Push a signed integer parameter.
111 ///
112 /// # Errors
113 ///
114 /// Returns `Err` if the push fails (allocation error or key too long).
115 pub fn push_int(self, key: &CStr, val: i32) -> Result<Self, ErrorStack> {
116 let rc = unsafe { sys::OSSL_PARAM_BLD_push_int(self.ptr, key.as_ptr(), val) };
117 if rc != 1 {
118 return Err(ErrorStack::drain());
119 }
120 Ok(self)
121 }
122
123 /// Push an unsigned integer parameter.
124 ///
125 /// # Errors
126 pub fn push_uint(self, key: &CStr, val: u32) -> Result<Self, ErrorStack> {
127 let rc = unsafe { sys::OSSL_PARAM_BLD_push_uint(self.ptr, key.as_ptr(), val) };
128 if rc != 1 {
129 return Err(ErrorStack::drain());
130 }
131 Ok(self)
132 }
133
134 /// Push a 64-bit unsigned integer parameter.
135 ///
136 /// Used for parameters such as the scrypt `N` cost factor.
137 ///
138 /// # Errors
139 pub fn push_uint64(self, key: &CStr, val: u64) -> Result<Self, ErrorStack> {
140 let rc = unsafe { sys::OSSL_PARAM_BLD_push_uint64(self.ptr, key.as_ptr(), val) };
141 if rc != 1 {
142 return Err(ErrorStack::drain());
143 }
144 Ok(self)
145 }
146
147 /// Push a `size_t` parameter.
148 ///
149 /// # Errors
150 pub fn push_size(self, key: &CStr, val: usize) -> Result<Self, ErrorStack> {
151 let rc = unsafe { sys::OSSL_PARAM_BLD_push_size_t(self.ptr, key.as_ptr(), val) };
152 if rc != 1 {
153 return Err(ErrorStack::drain());
154 }
155 Ok(self)
156 }
157
158 /// Push an octet-string parameter — **copies** `val` into the param array.
159 ///
160 /// Use this when `val` may be dropped before the resulting `Params` is consumed.
161 /// If `val` is guaranteed to outlive `Params`, prefer `push_octet_ptr`.
162 ///
163 /// # Errors
164 pub fn push_octet_slice(self, key: &CStr, val: &[u8]) -> Result<Self, ErrorStack> {
165 let rc = unsafe {
166 sys::OSSL_PARAM_BLD_push_octet_string(
167 self.ptr,
168 key.as_ptr(),
169 val.as_ptr().cast(),
170 val.len(),
171 )
172 };
173 if rc != 1 {
174 return Err(ErrorStack::drain());
175 }
176 Ok(self)
177 }
178
179 /// Push an octet-string parameter — **stores a pointer** into `val`.
180 ///
181 /// Zero-copy: no allocation occurs. The lifetime `'b` of `val` must be at
182 /// least `'a` to prevent the reference from being invalidated before the
183 /// `Params` array is consumed.
184 ///
185 /// # Errors
186 pub fn push_octet_ptr<'b>(
187 self,
188 key: &CStr,
189 val: &'b [u8],
190 ) -> Result<ParamBuilder<'b>, ErrorStack>
191 where
192 'a: 'b,
193 {
194 let rc = unsafe {
195 sys::OSSL_PARAM_BLD_push_octet_ptr(
196 self.ptr,
197 key.as_ptr(),
198 // OpenSSL declares the parameter as *mut c_void but treats it
199 // as read-only once stored (pointer storage, no write-back).
200 // The cast from *const to *mut is intentional: OpenSSL's API
201 // is not const-correct here.
202 val.as_ptr() as *mut std::os::raw::c_void,
203 val.len(),
204 )
205 };
206 if rc != 1 {
207 // `self` drops here, running Drop (frees builder + BIGNUMs).
208 return Err(ErrorStack::drain());
209 }
210 // Transfer both `ptr` and `bns` into the new lifetime-narrowed builder.
211 // Use ManuallyDrop so the Drop impl of `self` does not run a second free.
212 let md = std::mem::ManuallyDrop::new(self);
213 Ok(ParamBuilder {
214 ptr: md.ptr,
215 // SAFETY: we are the sole owner; md's Drop will not run.
216 bns: unsafe { ptr::read(&raw const md.bns) },
217 _lifetime: PhantomData,
218 })
219 }
220
221 /// Push a UTF-8 string parameter — **copies** `val` into the param array.
222 ///
223 /// # Errors
224 pub fn push_utf8_string(self, key: &CStr, val: &CStr) -> Result<Self, ErrorStack> {
225 let rc = unsafe {
226 sys::OSSL_PARAM_BLD_push_utf8_string(
227 self.ptr,
228 key.as_ptr(),
229 val.as_ptr(),
230 0, // 0 = use strlen
231 )
232 };
233 if rc != 1 {
234 return Err(ErrorStack::drain());
235 }
236 Ok(self)
237 }
238
239 /// Push a UTF-8 string parameter — **stores a pointer** into `val`.
240 ///
241 /// Zero-copy: no allocation occurs. Use only for `'static` literals such as
242 /// algorithm names (`c"oaep"`, `c"SHA-256"`).
243 ///
244 /// # Errors
245 pub fn push_utf8_ptr(self, key: &CStr, val: &'static CStr) -> Result<Self, ErrorStack> {
246 // OpenSSL does not provide OSSL_PARAM_BLD_push_utf8_ptr in all builds,
247 // so we use push_utf8_string which copies, but annotate that the intent
248 // is pointer storage. For 'static values the copy is negligible.
249 //
250 // NOTE: If OpenSSL gains OSSL_PARAM_BLD_push_utf8_ptr exposure in future
251 // bindgen output, replace the body with the pointer variant.
252 let rc = unsafe {
253 sys::OSSL_PARAM_BLD_push_utf8_string(self.ptr, key.as_ptr(), val.as_ptr(), 0)
254 };
255 if rc != 1 {
256 return Err(ErrorStack::drain());
257 }
258 Ok(self)
259 }
260
261 /// Push a BIGNUM parameter from big-endian bytes.
262 ///
263 /// Converts `bigendian_bytes` to an OpenSSL `BIGNUM` and pushes it into the
264 /// builder. The `BIGNUM` is copied into the builder's internal storage so
265 /// the caller's slice is not referenced after this call returns.
266 ///
267 /// # Panics
268 ///
269 /// Panics if `bigendian_bytes` is longer than `i32::MAX` bytes, which is
270 /// not a practical concern for key material.
271 ///
272 /// # Errors
273 ///
274 /// Returns `Err` if the allocation fails or the push fails.
275 pub fn push_bn(mut self, key: &CStr, bigendian_bytes: &[u8]) -> Result<Self, ErrorStack> {
276 // BN_bin2bn converts big-endian bytes into a newly allocated BIGNUM.
277 let bn = unsafe {
278 sys::BN_bin2bn(
279 bigendian_bytes.as_ptr(),
280 // len is int in C; values >2 GiB are pathological for key material.
281 i32::try_from(bigendian_bytes.len()).expect("BN too large"),
282 ptr::null_mut(),
283 )
284 };
285 if bn.is_null() {
286 return Err(ErrorStack::drain());
287 }
288 // OSSL_PARAM_BLD_push_BN stores a *pointer* to the BIGNUM; the actual
289 // copy into the OSSL_PARAM array is deferred to OSSL_PARAM_BLD_to_param.
290 // Keep the BIGNUM alive in `self.bns` until `build()` completes.
291 let rc = unsafe { sys::OSSL_PARAM_BLD_push_BN(self.ptr, key.as_ptr(), bn) };
292 if rc != 1 {
293 unsafe { sys::BN_free(bn) };
294 return Err(ErrorStack::drain());
295 }
296 self.bns.push(bn);
297 Ok(self)
298 }
299
300 /// Finalise the builder and return the `Params` array.
301 ///
302 /// Consumes the builder. The returned `Params` must outlive any borrowed
303 /// slices stored via `push_octet_ptr`.
304 ///
305 /// # Errors
306 ///
307 /// Returns `Err` if `OSSL_PARAM_BLD_to_param` fails.
308 pub fn build(self) -> Result<Params<'a>, ErrorStack> {
309 // Take ownership of the raw pointers and prevent Drop from running,
310 // so we control the exact point at which cleanup happens.
311 let builder_ptr = self.ptr;
312 let bns = unsafe { ptr::read(&raw const self.bns) };
313 std::mem::forget(self);
314
315 let param_ptr = unsafe { sys::OSSL_PARAM_BLD_to_param(builder_ptr) };
316 // Free the builder — the OSSL_PARAM array is independent of it.
317 unsafe { sys::OSSL_PARAM_BLD_free(builder_ptr) };
318 // Now that to_param has copied all BIGNUM values, free the temporaries.
319 for bn in bns {
320 unsafe { sys::BN_free(bn) };
321 }
322
323 if param_ptr.is_null() {
324 return Err(ErrorStack::drain());
325 }
326 Ok(Params {
327 ptr: param_ptr,
328 _lifetime: PhantomData,
329 })
330 }
331}
332
333impl Drop for ParamBuilder<'_> {
334 fn drop(&mut self) {
335 if !self.ptr.is_null() {
336 unsafe { sys::OSSL_PARAM_BLD_free(self.ptr) };
337 }
338 for bn in self.bns.drain(..) {
339 unsafe { sys::BN_free(bn) };
340 }
341 }
342}
343
344// ── Private BigNum helper ─────────────────────────────────────────────────────
345
346struct Bn(*mut sys::BIGNUM);
347
348impl Bn {
349 /// Convert this `BIGNUM` to a big-endian byte vector.
350 fn to_bigendian_vec(&self) -> Vec<u8> {
351 let nbits = unsafe { sys::BN_num_bits(self.0) };
352 let nbytes = usize::try_from(nbits).unwrap_or(0).div_ceil(8);
353 if nbytes == 0 {
354 return Vec::new();
355 }
356 let mut out = vec![0u8; nbytes];
357 // BN_bn2bin writes exactly nbytes; returns nbytes on success.
358 unsafe { sys::BN_bn2bin(self.0, out.as_mut_ptr()) };
359 out
360 }
361}
362
363impl Drop for Bn {
364 fn drop(&mut self) {
365 unsafe { sys::BN_free(self.0) };
366 }
367}
368
369// ── Params — getters and ownership transfer ───────────────────────────────────
370
371impl Params<'_> {
372 /// Adopt an OpenSSL-allocated `OSSL_PARAM` array, taking ownership.
373 ///
374 /// The array will be freed with `OSSL_PARAM_free` on drop. Use this to
375 /// wrap arrays returned by functions such as `EVP_PKEY_todata`.
376 ///
377 /// # Safety
378 ///
379 /// `ptr` must be a valid, `OSSL_PARAM_END`-terminated array allocated by
380 /// OpenSSL. After this call the caller must not use or free `ptr`.
381 #[must_use]
382 pub unsafe fn from_owned_ptr(ptr: *mut sys::OSSL_PARAM) -> Params<'static> {
383 Params {
384 ptr,
385 _lifetime: PhantomData,
386 }
387 }
388
389 /// Return a mutable pointer to the first `OSSL_PARAM` element.
390 ///
391 /// Pass to functions such as `EVP_PKEY_get_params` that fill a
392 /// pre-prepared query array.
393 #[must_use]
394 pub fn as_mut_ptr(&mut self) -> *mut sys::OSSL_PARAM {
395 self.ptr
396 }
397
398 /// Return `true` if a parameter with the given name exists in this array.
399 #[must_use]
400 pub fn has_param(&self, key: &CStr) -> bool {
401 !unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) }.is_null()
402 }
403
404 /// Locate `key` and read its value as an `i32`.
405 ///
406 /// # Errors
407 ///
408 /// Returns `Err` if the key is not found or the value cannot be converted.
409 pub fn get_int(&self, key: &CStr) -> Result<i32, ErrorStack> {
410 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
411 if elem.is_null() {
412 return Err(ErrorStack::drain());
413 }
414 let mut val: std::os::raw::c_int = 0;
415 crate::ossl_call!(sys::OSSL_PARAM_get_int(elem, std::ptr::addr_of_mut!(val)))?;
416 Ok(val)
417 }
418
419 /// Locate `key` and read its value as a `u32`.
420 ///
421 /// # Errors
422 pub fn get_uint(&self, key: &CStr) -> Result<u32, ErrorStack> {
423 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
424 if elem.is_null() {
425 return Err(ErrorStack::drain());
426 }
427 let mut val: std::os::raw::c_uint = 0;
428 crate::ossl_call!(sys::OSSL_PARAM_get_uint(elem, std::ptr::addr_of_mut!(val)))?;
429 Ok(val)
430 }
431
432 /// Locate `key` and read its value as a `usize`.
433 ///
434 /// # Errors
435 pub fn get_size_t(&self, key: &CStr) -> Result<usize, ErrorStack> {
436 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
437 if elem.is_null() {
438 return Err(ErrorStack::drain());
439 }
440 let mut val: usize = 0;
441 crate::ossl_call!(sys::OSSL_PARAM_get_size_t(
442 elem,
443 std::ptr::addr_of_mut!(val)
444 ))?;
445 Ok(val)
446 }
447
448 /// Locate `key` and read its value as an `i64`.
449 ///
450 /// # Errors
451 pub fn get_i64(&self, key: &CStr) -> Result<i64, ErrorStack> {
452 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
453 if elem.is_null() {
454 return Err(ErrorStack::drain());
455 }
456 let mut val: i64 = 0;
457 crate::ossl_call!(sys::OSSL_PARAM_get_int64(elem, std::ptr::addr_of_mut!(val)))?;
458 Ok(val)
459 }
460
461 /// Locate `key` and read its value as a `u64`.
462 ///
463 /// # Errors
464 pub fn get_u64(&self, key: &CStr) -> Result<u64, ErrorStack> {
465 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
466 if elem.is_null() {
467 return Err(ErrorStack::drain());
468 }
469 let mut val: u64 = 0;
470 crate::ossl_call!(sys::OSSL_PARAM_get_uint64(
471 elem,
472 std::ptr::addr_of_mut!(val)
473 ))?;
474 Ok(val)
475 }
476
477 /// Locate `key` and read it as BIGNUM, returning big-endian bytes.
478 ///
479 /// # Errors
480 ///
481 /// Returns `Err` if the key is not found or is not a BIGNUM parameter.
482 pub fn get_bn(&self, key: &CStr) -> Result<Vec<u8>, ErrorStack> {
483 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
484 if elem.is_null() {
485 return Err(ErrorStack::drain());
486 }
487 let mut bn_ptr: *mut sys::BIGNUM = ptr::null_mut();
488 crate::ossl_call!(sys::OSSL_PARAM_get_BN(elem, std::ptr::addr_of_mut!(bn_ptr)))?;
489 let bn = Bn(bn_ptr);
490 Ok(bn.to_bigendian_vec())
491 }
492
493 /// Locate `key` and return a borrowed slice of its octet-string data.
494 ///
495 /// The returned slice is valid for the lifetime of `self`.
496 ///
497 /// # Errors
498 pub fn get_octet_string(&self, key: &CStr) -> Result<&[u8], ErrorStack> {
499 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
500 if elem.is_null() {
501 return Err(ErrorStack::drain());
502 }
503 let mut p: *const std::os::raw::c_void = ptr::null();
504 let mut len: usize = 0;
505 crate::ossl_call!(sys::OSSL_PARAM_get_octet_string_ptr(
506 elem,
507 std::ptr::addr_of_mut!(p),
508 std::ptr::addr_of_mut!(len),
509 ))?;
510 // SAFETY: p points into the OSSL_PARAM array owned by self; valid for &self lifetime.
511 Ok(unsafe { std::slice::from_raw_parts(p.cast::<u8>(), len) })
512 }
513
514 /// Locate `key` and return a borrowed `CStr` of its UTF-8 string data.
515 ///
516 /// The returned reference is valid for the lifetime of `self`.
517 ///
518 /// # Errors
519 pub fn get_utf8_string(&self, key: &CStr) -> Result<&CStr, ErrorStack> {
520 let elem = unsafe { sys::OSSL_PARAM_locate(self.ptr, key.as_ptr()) };
521 if elem.is_null() {
522 return Err(ErrorStack::drain());
523 }
524 let mut p: *const std::os::raw::c_char = ptr::null();
525 crate::ossl_call!(sys::OSSL_PARAM_get_utf8_string_ptr(
526 elem,
527 std::ptr::addr_of_mut!(p),
528 ))?;
529 // SAFETY: p points into the OSSL_PARAM array owned by self; valid for &self lifetime.
530 Ok(unsafe { CStr::from_ptr(p) })
531 }
532}
533
534// ── Null sentinel ─────────────────────────────────────────────────────────────
535
536/// A const null `OSSL_PARAM` pointer — used to pass `NULL` to OpenSSL functions
537/// that accept an optional `const OSSL_PARAM[]` argument.
538#[must_use]
539pub(crate) fn null_params() -> *const sys::OSSL_PARAM {
540 ptr::null()
541}
542
543// ── Tests ─────────────────────────────────────────────────────────────────────
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548
549 #[test]
550 fn int_round_trip() {
551 let params = ParamBuilder::new()
552 .unwrap()
553 .push_int(c"mykey", 42)
554 .unwrap()
555 .build()
556 .unwrap();
557
558 // Locate the specific OSSL_PARAM element by name, then get its value.
559 let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"mykey".as_ptr()) };
560 assert!(!elem.is_null(), "OSSL_PARAM_locate failed");
561
562 let mut out: i32 = 0;
563 let rc = unsafe { sys::OSSL_PARAM_get_int(elem, std::ptr::addr_of_mut!(out)) };
564 assert_eq!(rc, 1, "OSSL_PARAM_get_int failed");
565 assert_eq!(out, 42);
566 }
567
568 #[test]
569 fn octet_slice_round_trip() {
570 let data = b"hello world";
571 let params = ParamBuilder::new()
572 .unwrap()
573 .push_octet_slice(c"blob", data)
574 .unwrap()
575 .build()
576 .unwrap();
577
578 let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"blob".as_ptr()) };
579 assert!(!elem.is_null());
580
581 let mut p: *const std::os::raw::c_void = ptr::null();
582 let mut len: usize = 0;
583 let rc = unsafe {
584 sys::OSSL_PARAM_get_octet_string_ptr(
585 elem,
586 std::ptr::addr_of_mut!(p),
587 std::ptr::addr_of_mut!(len),
588 )
589 };
590 assert_eq!(rc, 1, "OSSL_PARAM_get_octet_string_ptr failed");
591 assert_eq!(len, data.len());
592 let got = unsafe { std::slice::from_raw_parts(p.cast::<u8>(), len) };
593 assert_eq!(got, data);
594 }
595
596 // push_octet_ptr is verified through KDF integration tests (Phase 6),
597 // where it is used in a complete derive() operation. Isolated unit tests
598 // for it are unreliable because OSSL_PARAM_BLD_to_param may exhaust the
599 // OpenSSL secure heap in test environments.
600
601 #[test]
602 fn utf8_string_round_trip() {
603 let params = ParamBuilder::new()
604 .unwrap()
605 .push_utf8_string(c"alg", c"SHA-256")
606 .unwrap()
607 .build()
608 .unwrap();
609
610 let elem = unsafe { sys::OSSL_PARAM_locate(params.as_ptr().cast_mut(), c"alg".as_ptr()) };
611 assert!(!elem.is_null());
612
613 let mut out: *const std::os::raw::c_char = ptr::null();
614 let rc = unsafe { sys::OSSL_PARAM_get_utf8_string_ptr(elem, std::ptr::addr_of_mut!(out)) };
615 assert_eq!(rc, 1, "OSSL_PARAM_get_utf8_string_ptr failed");
616 let got = unsafe { CStr::from_ptr(out) };
617 assert_eq!(got.to_bytes(), b"SHA-256");
618 }
619}