1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// Copyright (C) 2021 Paolo Jovon <paolo.jovon@gmail.com>
// SPDX-License-Identifier: Apache-2.0

use crate::sys::*;
use log;
use std::{
    ffi::c_void,
    fmt::Debug,
    io::{Read, Seek, SeekFrom, Write},
    marker::PhantomData,
};

/// Represents a Rust byte stream, i.e. something [`Read`], [`Write`] and [`Seek`].
pub trait RWSeekable: Read + Write + Seek {
    /// Upcasts self to a `RWSeekable` reference.
    ///
    /// This is required for getting a fat pointer to `self` to be stored in the
    /// C-managed [`ktxStream`].
    fn as_mut_dyn(&mut self) -> &mut dyn RWSeekable;
}

impl<T: Read + Write + Seek> RWSeekable for T {
    fn as_mut_dyn(&mut self) -> &mut dyn RWSeekable {
        self
    }
}

impl<'a> Debug for dyn RWSeekable + 'a {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "RWSeekable({:p})", self)
    }
}

/// A Rust-based `ktxStream`, for reading from / writing to [`RWSeekable`]s.
#[allow(unused)]
pub struct RustKtxStream<'a, T: RWSeekable + ?Sized + 'a> {
    inner_ptr: Option<*mut T>,
    ktx_stream: Option<Box<ktxStream>>,
    ktx_phantom: PhantomData<&'a ktxStream>,
}

impl<'a, T: RWSeekable + ?Sized + 'a> RustKtxStream<'a, T> {
    /// Attempts to create a new Rust-based `ktxStream`, wrapping the given `inner` [`RWSeekable`].
    pub fn new(inner: Box<T>) -> Result<Self, ktx_error_code_e> {
        let inner_ptr = Box::into_raw(inner);
        // SAFETY: Safe, we just destructed a Box
        let inner_rwseekable_ptr = unsafe { (*inner_ptr).as_mut_dyn() } as *mut dyn RWSeekable;
        // SAFETY: Here be (rustc-version-dependent) dragons
        let (t_addr, vtable_addr): (*mut c_void, *mut c_void) =
            unsafe { std::mem::transmute(inner_rwseekable_ptr) };

        let ktx_stream = Box::new(ktxStream {
            read: Some(ktxRustStream_read),
            skip: Some(ktxRustStream_skip),
            write: Some(ktxRustStream_write),
            getpos: Some(ktxRustStream_getpos),
            setpos: Some(ktxRustStream_setpos),
            getsize: Some(ktxRustStream_getsize),
            destruct: Some(ktxRustStream_destruct),
            // Prevent the C API from messing with Rust structs
            closeOnDestruct: false,
            // SAFETY: This should be safe. The C API only sees an opaque handle at the end of the day.
            type_: streamType_eStreamTypeCustom,
            data: ktxStream__data {
                custom_ptr: ktxStream__custom_ptr {
                    address: t_addr,
                    allocatorAddress: vtable_addr,
                    size: 0,
                },
            },
            readpos: 0,
        });

        Ok(Self {
            inner_ptr: Some(inner_ptr),
            ktx_stream: Some(ktx_stream),
            ktx_phantom: PhantomData,
        })
    }

    /// Returns a handle to the underlying [`ktxStream`].
    ///
    /// ## Safety
    /// The returned handle is only for interaction with the C API.
    /// Do not modify this in any way if not absolutely necessary!
    pub fn ktx_stream(&self) -> *mut ktxStream {
        match &self.ktx_stream {
            // SAFETY - Safe. Even if C wants a mutable pointer.
            // This acts like a RefCell, where the normal interior mutability rules do not apply.
            Some(boxed) => unsafe { std::mem::transmute(boxed.as_ref()) },
            None => std::ptr::null_mut(),
        }
    }

    /// Returns a reference to the inner [`RWSeekable`].
    pub fn inner(&self) -> &T {
        // SAFETY: Safe if self has not been dropped
        unsafe { &*self.inner_ptr.expect("Self was destroyed") as &T }
    }

    /// Returns a mutable reference to the inner [`RWSeekable`].
    pub fn inner_mut(&mut self) -> &mut T {
        // SAFETY: Safe if self has not been dropped
        unsafe { &mut *self.inner_ptr.expect("Self was destroyed") as &mut T }
    }

    /// Zero out [`self.inner_ptr`], and re-box it to where it was before `new()`.
    fn rebox_inner_ptr(&mut self) -> Box<T> {
        // SAFETY: Safe-ish - a zeroed-out pointer is a null pointer in all supported platforms
        let moved_t = std::mem::replace(&mut self.inner_ptr, unsafe { std::mem::zeroed() });
        unsafe {
            // SAFETY: Safe - we're just reconstructing the box that was destructed in Self::new()
            Box::from_raw(moved_t.expect("Self was already destroyed"))
        }
    }

    /// Destroys self, giving back the boxed [`RWSeekable`] that was passed to [`Self::new`].
    pub fn into_inner(mut self) -> Box<T> {
        self.rebox_inner_ptr()
    }
}

impl<'a, T: RWSeekable + ?Sized + 'a> Drop for RustKtxStream<'a, T> {
    fn drop(&mut self) {
        // Firstly, this swaps self with a dummy
        let mut moved_self = std::mem::replace(
            self,
            RustKtxStream {
                inner_ptr: None,
                ktx_stream: None,
                ktx_phantom: PhantomData,
            },
        );

        // This is to mark the C-land `ktxStream` as invalid, and then to deallocate it
        if let Some(mut ktx_stream) = std::mem::replace(&mut moved_self.ktx_stream, None) {
            ktx_stream.data.custom_ptr = ktxStream__custom_ptr {
                address: std::ptr::null_mut(),
                allocatorAddress: std::ptr::null_mut(),
                size: 0xBADDA7A,
            };
            std::mem::drop(ktx_stream);
        }
        // The drop() of `ktx_stream` will do the rest

        // This is to destroy inner if `into_inner()` hasn't been called yet
        if let Some(_) = moved_self.inner_ptr {
            std::mem::drop(moved_self.rebox_inner_ptr())
        }

        // Finally, this prevents a drop cycle - IMPORTANT!
        // Note that we manually destroyed all fields above
        std::mem::forget(moved_self);
    }
}

fn format_option_ptr<T>(f: &mut std::fmt::Formatter<'_>, option: &Option<T>) -> std::fmt::Result {
    match option {
        Some(t) => write!(f, "{:p}", t),
        None => write!(f, "<none>"),
    }
}

impl<'a, T: RWSeekable + ?Sized + 'a> Debug for RustKtxStream<'a, T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "RustKtxStream(inner=")?;
        format_option_ptr(f, &self.inner_ptr)?;
        write!(f, ", ktxStream=")?;
        format_option_ptr(f, &self.ktx_stream)?;
        write!(f, ")")
    }
}

/// Get back a reference to the [`RWSeekable`] we put in `ktxStream.data.custom_ptr`. on RustKtxStream construction.
/// SAFETY: UB if `str` is not actually a pointer to a [`RustKtxStream`].
unsafe fn inner_rwseekable<'a>(str: *mut ktxStream) -> &'a mut dyn RWSeekable {
    let t_addr = (*str).data.custom_ptr.address;
    let vtable_addr = (*str).data.custom_ptr.allocatorAddress;
    let fat_t_ptr = (t_addr, vtable_addr);
    let inner_ref: *mut dyn RWSeekable = std::mem::transmute(fat_t_ptr);
    &mut *inner_ref
}

// Since `#[feature(seek_stream_len)]` is unstable...
fn stream_len(seek: &mut dyn RWSeekable) -> std::io::Result<u64> {
    let old_pos = seek.stream_position()?;
    let size = seek.seek(SeekFrom::End(0))?;
    seek.seek(SeekFrom::Start(old_pos))?;
    Ok(size)
}

#[no_mangle]
unsafe extern "C" fn ktxRustStream_read(
    str: *mut ktxStream,
    dst: *mut c_void,
    count: ktx_size_t,
) -> ktx_error_code_e {
    let inner = inner_rwseekable(str);
    let buf = std::slice::from_raw_parts_mut(dst as *mut u8, count as usize);
    match inner.read_exact(buf) {
        Ok(_) => ktx_error_code_e_KTX_SUCCESS,
        Err(err) => {
            log::error!("ktxRustStream_read: {}", err);
            ktx_error_code_e_KTX_FILE_READ_ERROR
        }
    }
}

#[no_mangle]
unsafe extern "C" fn ktxRustStream_skip(
    str: *mut ktxStream,
    count: ktx_size_t,
) -> ktx_error_code_e {
    let inner = inner_rwseekable(str);
    match inner.seek(SeekFrom::Current(count as i64)) {
        Ok(_) => ktx_error_code_e_KTX_SUCCESS,
        Err(err) => {
            log::error!("ktxRustStream_skip: {}", err);
            ktx_error_code_e_KTX_FILE_SEEK_ERROR
        }
    }
}

#[no_mangle]
unsafe extern "C" fn ktxRustStream_write(
    str: *mut ktxStream,
    src: *const c_void,
    size: ktx_size_t,
    count: ktx_size_t,
) -> ktx_error_code_e {
    let inner = inner_rwseekable(str);
    let len = (size * count) as usize;
    let buf = std::slice::from_raw_parts(src as *const u8, len);
    match inner.write_all(buf) {
        Ok(_) => ktx_error_code_e_KTX_SUCCESS,
        Err(err) => {
            log::error!("ktxRustStream_write: {}", err);
            ktx_error_code_e_KTX_FILE_WRITE_ERROR
        }
    }
}

#[no_mangle]
unsafe extern "C" fn ktxRustStream_getpos(
    str: *mut ktxStream,
    pos: *mut ktx_off_t,
) -> ktx_error_code_e {
    let inner = inner_rwseekable(str);
    match inner.stream_position() {
        Ok(cur) => {
            *pos = cur as ktx_off_t;
            ktx_error_code_e_KTX_SUCCESS
        }
        Err(err) => {
            log::error!("ktxRustStream_getpos: {}", err);
            ktx_error_code_e_KTX_FILE_SEEK_ERROR
        }
    }
}

#[no_mangle]
unsafe extern "C" fn ktxRustStream_setpos(str: *mut ktxStream, off: ktx_off_t) -> ktx_error_code_e {
    let inner = inner_rwseekable(str);
    match inner.seek(SeekFrom::Start(off as u64)) {
        Ok(_) => ktx_error_code_e_KTX_SUCCESS,
        Err(err) => {
            log::error!("ktxRustStream_setpos: {}", err);
            ktx_error_code_e_KTX_FILE_SEEK_ERROR
        }
    }
}

#[no_mangle]
unsafe extern "C" fn ktxRustStream_getsize(
    str: *mut ktxStream,
    size: *mut ktx_size_t,
) -> ktx_error_code_e {
    let inner = inner_rwseekable(str);
    match stream_len(inner) {
        Ok(len) => {
            *size = len as ktx_size_t;
            ktx_error_code_e_KTX_SUCCESS
        }
        Err(err) => {
            log::error!("ktxRustStream_getsize: {}", err);
            ktx_error_code_e_KTX_FILE_SEEK_ERROR
        }
    }
}

#[no_mangle]
unsafe extern "C" fn ktxRustStream_destruct(_str: *mut ktxStream) {
    // No-op; `RustKtxStream::drop()` will do all the work.
}