outlook_mapi/
mapi_ptr.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT license.
3
4//! Define [`MAPIUninit`], [`MAPIBuffer`], and [`MAPIOutParam`].
5//!
6//! Smart pointer types for memory allocated with [`sys::MAPIAllocateBuffer`], which must be freed
7//! with [`sys::MAPIFreeBuffer`], or [`sys::MAPIAllocateMore`], which is chained to another
8//! allocation and must not outlive that allocation or be separately freed.
9
10use crate::sys;
11use core::{
12    ffi,
13    marker::PhantomData,
14    mem::{self, MaybeUninit},
15    ptr, slice,
16};
17use windows::Win32::Foundation::E_OUTOFMEMORY;
18use windows_core::{Error, HRESULT};
19
20/// Errors which can be returned from this module.
21#[derive(Debug)]
22pub enum MAPIAllocError {
23    /// The underlying [`sys::MAPIAllocateBuffer`] and [`sys::MAPIAllocateMore`] take a `u32`
24    /// parameter specifying the size of the buffer. If you exceed the capacity of a `u32`, it will
25    /// be impossible to make the necessary allocation.
26    SizeOverflow(usize),
27
28    /// MAPI APIs like to work with input and output buffers using `*const u8` and `*mut u8` rather
29    /// than strongly typed pointers. In C++, this requires a lot of `reinterpret_cast` operations.
30    /// For the accessors on this type, we'll allow transmuting the buffer to the desired type, as
31    /// long as it fits in the allocation. If you don't allocate enough space for the number of
32    /// elements you are accessing, it will return this error.
33    OutOfBoundsAccess,
34
35    /// There are no [documented](https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/mapiallocatebuffer)
36    /// conditions where [`sys::MAPIAllocateBuffer`] or [`sys::MAPIAllocateMore`] will return an
37    /// error, but if they do fail, this will propagate the [`Error`] result. If the allocation
38    /// function returns `null` with no other error, it will treat that as [`E_OUTOFMEMORY`].
39    AllocationFailed(Error),
40}
41
42enum Buffer<T>
43where
44    T: Sized,
45{
46    Uninit(*mut MaybeUninit<T>),
47    Ready(*mut T),
48}
49
50enum Allocation<'a, T>
51where
52    T: Sized,
53{
54    Root {
55        buffer: Buffer<T>,
56        byte_count: usize,
57    },
58    More {
59        buffer: Buffer<T>,
60        byte_count: usize,
61        root: *mut ffi::c_void,
62        phantom: PhantomData<&'a T>,
63    },
64}
65
66impl<'a, T> Allocation<'a, T>
67where
68    T: Sized,
69{
70    fn new(count: usize) -> Result<Self, MAPIAllocError> {
71        let byte_count = count * mem::size_of::<T>();
72        Ok(Self::Root {
73            buffer: unsafe {
74                let mut alloc = ptr::null_mut();
75                HRESULT::from_win32(sys::MAPIAllocateBuffer(
76                    u32::try_from(byte_count)
77                        .map_err(|_| MAPIAllocError::SizeOverflow(byte_count))?,
78                    &mut alloc,
79                ) as u32)
80                .ok()
81                .map_err(MAPIAllocError::AllocationFailed)?;
82                if alloc.is_null() {
83                    return Err(MAPIAllocError::AllocationFailed(Error::from_hresult(
84                        E_OUTOFMEMORY,
85                    )));
86                }
87                Buffer::Uninit(alloc as *mut _)
88            },
89            byte_count,
90        })
91    }
92
93    fn chain<P>(&self, count: usize) -> Result<Allocation<'a, P>, MAPIAllocError>
94    where
95        P: Sized,
96    {
97        let root = match self {
98            Self::Root { buffer, .. } => match buffer {
99                Buffer::Uninit(alloc) => *alloc as *mut _,
100                Buffer::Ready(alloc) => *alloc as *mut _,
101            },
102            Self::More { root, .. } => *root,
103        };
104        let byte_count = count * mem::size_of::<T>();
105        Ok(Allocation::More {
106            buffer: unsafe {
107                let mut alloc = ptr::null_mut();
108                HRESULT::from_win32(sys::MAPIAllocateMore(
109                    u32::try_from(byte_count)
110                        .map_err(|_| MAPIAllocError::SizeOverflow(byte_count))?,
111                    root,
112                    &mut alloc,
113                ) as u32)
114                .ok()
115                .map_err(MAPIAllocError::AllocationFailed)?;
116                if alloc.is_null() {
117                    return Err(MAPIAllocError::AllocationFailed(Error::from_hresult(
118                        E_OUTOFMEMORY,
119                    )));
120                }
121                Buffer::Uninit(alloc as *mut _)
122            },
123            byte_count,
124            root,
125            phantom: PhantomData,
126        })
127    }
128
129    fn into<P>(self) -> Result<Allocation<'a, P>, MAPIAllocError> {
130        let result = match self {
131            Self::Root {
132                buffer: Buffer::Ready(_),
133                ..
134            }
135            | Self::More {
136                buffer: Buffer::Ready(_),
137                ..
138            } => unreachable!(),
139            Self::Root {
140                buffer: Buffer::Uninit(alloc),
141                byte_count,
142            } if byte_count >= mem::size_of::<T>() => Ok(Allocation::Root {
143                buffer: Buffer::Uninit(alloc as *mut _),
144                byte_count,
145            }),
146            Self::More {
147                buffer: Buffer::Uninit(alloc),
148                byte_count,
149                root,
150                ..
151            } if byte_count >= mem::size_of::<T>() => Ok(Allocation::More {
152                buffer: Buffer::Uninit(alloc as *mut _),
153                byte_count,
154                root,
155                phantom: PhantomData,
156            }),
157            _ => Err(MAPIAllocError::OutOfBoundsAccess),
158        };
159        if result.is_ok() {
160            mem::forget(self);
161        }
162        result
163    }
164
165    fn iter(&self) -> AllocationIter<'a, T> {
166        match self {
167            Self::Root {
168                buffer: Buffer::Uninit(alloc),
169                byte_count,
170            } => AllocationIter {
171                alloc: *alloc,
172                byte_count: *byte_count,
173                element_size: mem::size_of::<T>(),
174                root: *alloc as *mut _,
175                phantom: PhantomData,
176            },
177            Self::More {
178                buffer: Buffer::Uninit(alloc),
179                byte_count,
180                root,
181                ..
182            } => AllocationIter {
183                alloc: *alloc,
184                byte_count: *byte_count,
185                element_size: mem::size_of::<T>(),
186                root: *root,
187                phantom: PhantomData,
188            },
189            _ => unreachable!(),
190        }
191    }
192
193    fn uninit(&mut self) -> Result<&mut MaybeUninit<T>, MAPIAllocError> {
194        match self {
195            Self::Root {
196                buffer: Buffer::Ready(_),
197                ..
198            }
199            | Self::More {
200                buffer: Buffer::Ready(_),
201                ..
202            } => unreachable!(),
203            Self::Root {
204                buffer: Buffer::Uninit(alloc),
205                byte_count,
206            } if mem::size_of::<T>() <= *byte_count => Ok(unsafe { &mut *(*alloc) }),
207            Self::More {
208                buffer: Buffer::Uninit(alloc),
209                byte_count,
210                ..
211            } if mem::size_of::<T>() <= *byte_count => Ok(unsafe { &mut *(*alloc) }),
212            _ => Err(MAPIAllocError::OutOfBoundsAccess),
213        }
214    }
215
216    unsafe fn assume_init(self) -> Self {
217        let result = match self {
218            Self::Root {
219                buffer: Buffer::Uninit(alloc),
220                byte_count,
221            } => Self::Root {
222                buffer: Buffer::Ready(alloc as *mut _),
223                byte_count,
224            },
225            Self::More {
226                buffer: Buffer::Uninit(alloc),
227                byte_count,
228                root,
229                ..
230            } => Self::More {
231                buffer: Buffer::Ready(alloc as *mut _),
232                byte_count,
233                root,
234                phantom: PhantomData,
235            },
236            _ => unreachable!(),
237        };
238        mem::forget(self);
239        result
240    }
241
242    fn as_mut(&mut self) -> Result<&mut T, MAPIAllocError> {
243        match self {
244            Self::Root {
245                buffer: Buffer::Uninit(_),
246                ..
247            }
248            | Self::More {
249                buffer: Buffer::Uninit(_),
250                ..
251            } => unreachable!(),
252            Self::Root {
253                buffer: Buffer::Ready(alloc),
254                byte_count,
255            } if mem::size_of::<T>() <= *byte_count => Ok(unsafe { &mut *(*alloc) }),
256            Self::More {
257                buffer: Buffer::Ready(alloc),
258                byte_count,
259                ..
260            } if mem::size_of::<T>() <= *byte_count => Ok(unsafe { &mut *(*alloc) }),
261            _ => Err(MAPIAllocError::OutOfBoundsAccess),
262        }
263    }
264}
265
266impl<T> Drop for Allocation<'_, T> {
267    fn drop(&mut self) {
268        if let Self::Root { buffer, .. } = self {
269            let alloc = match mem::replace(buffer, Buffer::Uninit(ptr::null_mut())) {
270                Buffer::Uninit(alloc) => alloc as *mut T,
271                Buffer::Ready(alloc) => alloc,
272            };
273            if !alloc.is_null() {
274                #[cfg(test)]
275                unreachable!();
276                #[cfg(not(test))]
277                unsafe {
278                    sys::MAPIFreeBuffer(alloc as *mut _);
279                }
280            }
281        }
282    }
283}
284
285struct AllocationIter<'a, T>
286where
287    T: Sized,
288{
289    alloc: *mut MaybeUninit<T>,
290    byte_count: usize,
291    root: *mut ffi::c_void,
292    element_size: usize,
293    phantom: PhantomData<&'a T>,
294}
295
296impl<'a, T> Iterator for AllocationIter<'a, T>
297where
298    T: Sized,
299{
300    type Item = Allocation<'a, T>;
301
302    fn next(&mut self) -> Option<Self::Item> {
303        if self.byte_count < self.element_size {
304            return None;
305        }
306
307        let item = Allocation::More {
308            buffer: Buffer::Uninit(self.alloc),
309            byte_count: self.element_size,
310            root: self.root,
311            phantom: PhantomData,
312        };
313
314        self.byte_count -= self.element_size;
315        self.alloc = unsafe { self.alloc.add(1) };
316
317        Some(item)
318    }
319}
320
321/// Wrapper type for an allocation with either [`sys::MAPIAllocateBuffer`] or
322/// [`sys::MAPIAllocateMore`] which has not been initialized yet.
323pub struct MAPIUninit<'a, T>(Allocation<'a, T>)
324where
325    T: Sized;
326
327impl<'a, T> MAPIUninit<'a, T> {
328    /// Create a new allocation with enough room for `count` elements of type `T` with a call to
329    /// [`sys::MAPIAllocateBuffer`]. The buffer is freed as soon as the [`MAPIUninit`] or
330    /// [`MAPIBuffer`] is dropped.
331    ///
332    /// If you call [`MAPIUninit::chain`] to create any more allocations with
333    /// [`sys::MAPIAllocateMore`], their lifetimes are constrained to the lifetime of this
334    /// allocation and they will all be freed together in a single call to [`sys::MAPIFreeBuffer`].
335    pub fn new(count: usize) -> Result<Self, MAPIAllocError> {
336        Ok(Self(Allocation::new(count)?))
337    }
338
339    /// Create a new allocation with enough room for `count` elements of type `P` with a call to
340    /// [`sys::MAPIAllocateMore`]. The result is a separate allocation that is not freed until
341    /// `self` is dropped at the beginning of the chain.
342    ///
343    /// You may call [`MAPIUninit::chain`] on the result as well, they will both share a root
344    /// allocation created with [`MAPIUninit::new`].
345    pub fn chain<P>(&self, count: usize) -> Result<MAPIUninit<'a, P>, MAPIAllocError> {
346        Ok(MAPIUninit::<'a, P>(self.0.chain::<P>(count)?))
347    }
348
349    /// Convert an uninitialized allocation to another type. You can use this, for example, to
350    /// perform an allocation with extra space in a `&mut [u8]` buffer, and then cast that to a
351    /// specific type. This is useful with the `CbNewXXX` functions in [`crate::sized_types`].
352    pub fn into<P>(self) -> Result<MAPIUninit<'a, P>, MAPIAllocError> {
353        Ok(MAPIUninit::<'a, P>(self.0.into::<P>()?))
354    }
355
356    /// Get an iterator over the uninitialized elements.
357    pub fn iter(&self) -> MAPIUninitIter<'a, T> {
358        MAPIUninitIter(self.0.iter())
359    }
360
361    /// Get an uninitialized out-parameter with enough room for a single element of type `T`.
362    pub fn uninit(&mut self) -> Result<&mut MaybeUninit<T>, MAPIAllocError> {
363        self.0.uninit()
364    }
365
366    /// Once the buffer is known to be completely filled in, convert this [`MAPIUninit`] to a
367    /// fully initialized [`MAPIBuffer`].
368    ///
369    /// # Safety
370    ///
371    /// Like [`MaybeUninit`], the caller must ensure that the buffer is completely initialized
372    /// before calling [`MAPIUninit::assume_init`]. It is undefined behavior to leave it
373    /// uninitialized once we start accessing it.
374    pub unsafe fn assume_init(self) -> MAPIBuffer<'a, T> {
375        MAPIBuffer(unsafe { self.0.assume_init() })
376    }
377}
378
379/// Iterator over the uninitialized elements in a [`MAPIUninit`] allocation.
380pub struct MAPIUninitIter<'a, T>(AllocationIter<'a, T>)
381where
382    T: Sized;
383
384impl<'a, T> Iterator for MAPIUninitIter<'a, T>
385where
386    T: Sized,
387{
388    type Item = MAPIUninit<'a, T>;
389
390    fn next(&mut self) -> Option<Self::Item> {
391        self.0.next().map(MAPIUninit)
392    }
393}
394
395/// Wrapper type for an allocation in [`MAPIUninit`] which has been fully initialized.
396pub struct MAPIBuffer<'a, T>(Allocation<'a, T>)
397where
398    T: Sized;
399
400impl<'a, T> MAPIBuffer<'a, T> {
401    /// Create a new allocation with enough room for `count` elements of type `P` with a call to
402    /// [`sys::MAPIAllocateMore`]. The result is a separate allocation that is not freed until
403    /// `self` is dropped at the beginning of the chain.
404    ///
405    /// You may call [`MAPIBuffer::chain`] on the result as well, they will both share a root
406    /// allocation created with [`MAPIUninit::new`].
407    pub fn chain<P>(&self, count: usize) -> Result<MAPIUninit<'a, P>, MAPIAllocError> {
408        Ok(MAPIUninit::<'a, P>(self.0.chain::<P>(count)?))
409    }
410
411    /// Access a single element of type `T` once it has been initialized with
412    /// [`MAPIUninit::assume_init`].
413    pub fn as_mut(&mut self) -> Result<&mut T, MAPIAllocError> {
414        self.0.as_mut()
415    }
416}
417
418/// Hold an out-pointer for MAPI APIs which perform their own buffer allocations. This version does
419/// not perform any validation of the buffer size, so the typed accessors are inherently unsafe.
420pub struct MAPIOutParam<T>(*mut T)
421where
422    T: Sized;
423
424impl<T> MAPIOutParam<T>
425where
426    T: Sized,
427{
428    /// Get a `*mut *mut T` suitable for use with a MAPI API that fills in an out-pointer
429    /// with a newly allocated buffer.
430    pub fn as_mut_ptr(&mut self) -> *mut *mut T {
431        &mut self.0
432    }
433
434    /// Access a single element of type `T`.
435    ///
436    /// # Safety
437    ///
438    /// This version does not perform any validation of the buffer size, so the typed accessors are
439    /// inherently unsafe. The only thing it handles is a `null` check.
440    pub unsafe fn as_mut(&mut self) -> Option<&mut T> {
441        unsafe { self.0.as_mut() }
442    }
443
444    /// Access a slice with `count` elements of type `T`.
445    ///
446    /// # Safety
447    ///
448    /// This version does not perform any validation of the buffer size, so the typed accessors are
449    /// inherently unsafe. The only thing it handles is a `null` check.
450    pub unsafe fn as_mut_slice(&mut self, count: usize) -> Option<&mut [T]> {
451        if self.0.is_null() {
452            None
453        } else {
454            Some(unsafe { slice::from_raw_parts_mut(self.0, count) })
455        }
456    }
457}
458
459impl<T> Default for MAPIOutParam<T>
460where
461    T: Sized,
462{
463    fn default() -> Self {
464        Self(ptr::null_mut())
465    }
466}
467
468impl<T> Drop for MAPIOutParam<T>
469where
470    T: Sized,
471{
472    fn drop(&mut self) {
473        if !self.0.is_null() {
474            #[cfg(test)]
475            unreachable!();
476            #[cfg(not(test))]
477            unsafe {
478                sys::MAPIFreeBuffer(self.0 as *mut _);
479            }
480        }
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use super::*;
487    use crate::*;
488
489    use mem::ManuallyDrop;
490
491    SizedSPropTagArray! { TestTags[2] }
492
493    const TEST_TAGS: TestTags = TestTags {
494        cValues: 2,
495        aulPropTag: [sys::PR_INSTANCE_KEY, sys::PR_SUBJECT_W],
496    };
497
498    #[test]
499    fn buffer_uninit() {
500        let mut buffer: MaybeUninit<TestTags> = MaybeUninit::uninit();
501        let mut mapi_buffer = ManuallyDrop::new(MAPIUninit(Allocation::Root {
502            buffer: Buffer::Uninit(&mut buffer),
503            byte_count: mem::size_of_val(&buffer),
504        }));
505        assert!(mapi_buffer.uninit().is_ok());
506    }
507
508    #[test]
509    fn buffer_into() {
510        let mut buffer: [MaybeUninit<u8>; mem::size_of::<TestTags>()] =
511            [MaybeUninit::uninit(); CbNewSPropTagArray(2)];
512        let mut mapi_buffer = ManuallyDrop::new(MAPIUninit(Allocation::Root {
513            buffer: Buffer::Uninit(buffer.as_mut_ptr()),
514            byte_count: buffer.len(),
515        }));
516        assert!(mapi_buffer.uninit().is_ok());
517        let mut mapi_buffer = ManuallyDrop::new(
518            ManuallyDrop::into_inner(mapi_buffer)
519                .into::<TestTags>()
520                .expect("into failed"),
521        );
522        assert!(mapi_buffer.uninit().is_ok());
523    }
524
525    #[test]
526    fn buffer_iter() {
527        let mut buffer: [MaybeUninit<u32>; 2] = [MaybeUninit::uninit(); 2];
528        let mapi_buffer = ManuallyDrop::new(MAPIUninit(Allocation::Root {
529            buffer: Buffer::Uninit(buffer.as_mut_ptr()),
530            byte_count: buffer.len() * mem::size_of::<u32>(),
531        }));
532        let mut next = mapi_buffer.iter();
533        assert!(match next.next() {
534            Some(MAPIUninit(Allocation::More {
535                buffer: Buffer::Uninit(alloc),
536                byte_count,
537                root,
538                ..
539            })) => {
540                assert_eq!(alloc, buffer.as_mut_ptr() as *mut _);
541                assert_eq!(root, buffer.as_mut_ptr() as *mut _);
542                assert_eq!(byte_count, mem::size_of::<u32>());
543                true
544            }
545            _ => false,
546        });
547        assert!(match next.next() {
548            Some(MAPIUninit(Allocation::More {
549                buffer: Buffer::Uninit(alloc),
550                byte_count,
551                root,
552                ..
553            })) => {
554                assert_eq!(alloc, unsafe { buffer.as_mut_ptr().add(1) } as *mut _);
555                assert_eq!(root, buffer.as_mut_ptr() as *mut _);
556                assert_eq!(byte_count, mem::size_of::<u32>());
557                true
558            }
559            _ => false,
560        });
561        assert!(next.next().is_none());
562    }
563
564    #[test]
565    fn buffer_assume_init() {
566        let mut buffer = MaybeUninit::uninit();
567        let mapi_buffer = ManuallyDrop::new(MAPIUninit(Allocation::Root {
568            buffer: Buffer::Uninit(&mut buffer),
569            byte_count: mem::size_of_val(&buffer),
570        }));
571        buffer.write(TEST_TAGS);
572        let mut mapi_buffer =
573            ManuallyDrop::new(unsafe { ManuallyDrop::into_inner(mapi_buffer).assume_init() });
574        let test_tags = mapi_buffer.as_mut().expect("as_mut failed");
575        assert_eq!(TEST_TAGS.cValues, test_tags.cValues);
576        assert_eq!(TEST_TAGS.aulPropTag, test_tags.aulPropTag);
577    }
578}