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
#![allow(dead_code)] // We may not need to use functions we export
#[macro_use]
extern crate bitflags;
extern crate winapi;
extern crate serde;
extern crate serde_yaml;
extern crate encoding;
extern crate libc;

pub mod session;
pub mod telemetry;
pub mod track_surface;
pub mod states;

use crate::session::*;
use serde_yaml::from_str as yaml_from;
use std::ptr::null;
use std::io::Error;
use std::slice::from_raw_parts;
use std::io::Result as IOResult;
use std::mem::transmute;
use std::sync::Mutex;
use libc::c_void;
use encoding::{Encoding, DecoderTrap};
use encoding::all::ISO_8859_1;

#[cfg(target_os="windows")]
use crate::telemetry::Header;

#[cfg(target_os="windows")]
use std::os::windows::raw::HANDLE;
#[cfg(target_os="windows")]
use winapi::shared::minwindef::LPVOID;
#[cfg(target_os="windows")]
use winapi::um::errhandlingapi::GetLastError;
#[cfg(target_os="windows")]
use winapi::um::handleapi::CloseHandle;
#[cfg(target_os="windows")]
use winapi::um::memoryapi::{OpenFileMappingW, FILE_MAP_READ, MapViewOfFile};


pub const TELEMETRY_PATH: &'static str = "Local\\IRSDKMemMapFileName";
pub const UNLIMITED_LAPS: i32 = 32767;
pub const UNLIMITED_TIME: f32 = 604800.0;


#[cfg(target_os="windows")]
///
/// iRacing live telemetry and session data connection.
/// 
/// Allows retrival of live data fro iRacing.
/// The data is provided using a shared memory map, allowing the simulator
/// to deposit data roughly every 16ms to be read.
/// 
/// # Examples
/// 
/// ```
/// use iracing::Connection;
/// 
/// let _ = Connection::new().expect("Unable to find telemetry data");
/// ```
pub struct Connection {
    mux: Mutex<()>,
    location: *mut c_void,
    header: Header
}

#[cfg(target_os = "windows")]
impl Connection {
    pub fn new() -> IOResult<Connection> {
        let mut path: Vec<u16> = TELEMETRY_PATH.encode_utf16().collect();
        path.push(0);

        let mapping: HANDLE;
        let errno: i32;

        unsafe { mapping = OpenFileMappingW(FILE_MAP_READ, 0, path.as_ptr()); };

        if null() == mapping {
            
            unsafe { errno = GetLastError() as i32; }

            return Err(Error::from_raw_os_error(errno));
        }

        let view: LPVOID;
 
        unsafe {
            view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
        }

        if null() == view {
            unsafe { errno = GetLastError() as i32; }

            return Err(Error::from_raw_os_error(errno))
        }

        let header = unsafe { Self::read_header(view) };

        return Ok(Connection {mux: Mutex::default(), location: view, header: header});
    }

    ///
    /// Get the data header
    /// 
    /// Reads the data header from the shared memory map and returns a copy of the header
    /// which can be used safely elsewhere.
    /// 
    /// # Examples
    /// 
    /// ```
    /// use iracing::Connection;
    /// 
    /// let location_of_an_iracing_header: *const c_void;
    /// let header = Connection::read_header(location_of_an_iracing_header);
    /// 
    /// println!("Data Version: {}", header.version);
    /// ```
    pub unsafe fn read_header(from: *const c_void) -> Header {
        let raw_header: *const Header = transmute(from);
        let h: Header = *raw_header;

        h.clone()
    }

    ///
    /// Get session information
    /// 
    /// Get general session information - This data is mostly static and contains
    /// overall information related to the current or replayed session
    /// 
    /// # Examples
    /// 
    /// ```
    /// use iracing::Connection;
    /// 
    /// match Connection::new().expect("Unable to open session").session_info() {
    ///     Ok(session) => println!("Track Name: {}", session.weekend.track_display_name),
    ///     Err(e) => println!("Invalid Session")
    /// };
    /// ```
    pub fn session_info(&mut self) -> Result<SessionDetails, Box<dyn std::error::Error>> {
        let header = unsafe { Self::read_header(self.location) };

        let start = (self.location as usize + header.session_info_offset as usize) as *const u8;
        let size = header.session_info_length as usize;
        
        let data: &[u8] = unsafe { from_raw_parts(start, size) };

        // Decode the data as ISO-8859-1 (Rust wants UTF-8)
        let content: String = match ISO_8859_1.decode(data, DecoderTrap::Strict) {
            Ok(value) => value,
            Err(e) => return Err(Box::from(e))
        };

        match yaml_from(content.as_str()) {
            Ok(session) => Ok(session),
            Err(e) => Err(Box::from(e))
        }
    }

    ///
    /// Get latest telemetry.
    /// 
    /// Get the latest live telemetry data, the telemetry is updated roughtly every 16ms
    /// 
    /// # Examples
    /// 
    /// ```
    /// use iracing::Connection;
    /// 
    /// let sample = Connection::new()?.telemetry()?;
    /// ``` 
    pub fn telemetry(&self) -> Result<telemetry::Sample, Box<dyn std::error::Error>> {
        let header = unsafe { Self::read_header(self.location) };
        header.telemetry(self.location as *const std::ffi::c_void)
    }

    ///
    /// Get Blocking Telemetry Interface.
    /// 
    /// Creates a new `iracing::telemetry::Blocking` connector which allows telemetry samples to
    /// be collected, and will wait and block until a new sample is available, or a timeout is reached.
    /// 
    /// # Examples
    /// 
    /// ```
    /// use iracing::Connection;
    /// use std::time::Duration;
    /// 
    /// let sampler = Connection::new()?.blocking()?;
    /// let sample = sample.sample(Duration::from_millis(50))?;
    /// ```
    pub fn blocking(&self) -> IOResult<telemetry::Blocking> {
        telemetry::Blocking::new(self.location, unsafe { Self::read_header(self.location) })
    }

    pub fn close(&self) -> IOResult<()> {
        let succ = unsafe { CloseHandle(self.location) };

        if succ != 0 {
            Ok(())
        } else {
            let errno: i32 = unsafe { GetLastError() as i32 };

            Err(Error::from_raw_os_error(errno))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_session_info() {
        match Connection::new().expect("Unable to open telemetry").session_info() {
            Ok(session) => println!("Track: {}", session.weekend.track_name),
            Err(e) => println!("Error: {:?}", e)
        };
    }

    #[test]
    fn test_latest_telemetry() {
        Connection::new().expect("Unable to open telemetry").telemetry().expect("Couldn't get latest telem");
    }
}