Skip to main content

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