intercom 0.4.0

Utilities for writing COM visible Rust components.
Documentation
// <3 winapi
// (Re-defining these here as not to pull the whole winapi as runtime dependency)

/// Binary GUID format as defined for the COM interfaces.
#[repr(C)]
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct GUID
{
    pub data1: u32,
    pub data2: u16,
    pub data3: u16,
    pub data4: [u8; 8],
}

impl GUID
{
    pub fn zero_guid() -> GUID
    {
        GUID {
            data1: 0,
            data2: 0,
            data3: 0,
            data4: [0; 8],
        }
    }

    /// Parses the given string as a GUID.
    ///
    /// Supported formats include:
    ///
    /// - Braces and hyphens: {00000000-0000-0000-0000-000000000000}
    /// - Hyphens only: 00000000-0000-0000-0000-000000000000
    /// - Raw hexadecimal: 00000000000000000000000000000000
    pub fn parse(guid: &str) -> Result<GUID, String>
    {
        // We support the following formats:
        //
        // - {00000000-0000-0000-0000-000000000000} (38 chars)
        // - 00000000-0000-0000-0000-000000000000 (36 chars)
        // - 00000000000000000000000000000000 (32 chars)
        //
        // Use the total length to make the assumption on the format.
        enum GuidFormat
        {
            Braces,
            Hyphens,
            Raw,
        }
        let guid_format = match guid.len() {
            38 => GuidFormat::Braces,
            36 => GuidFormat::Hyphens,
            32 => GuidFormat::Raw,
            _ => {
                return Err(format!(
                    "Unrecognized GUID format: '{}' ({})",
                    guid,
                    guid.len()
                ))
            }
        };

        // Define the format requirements.
        //
        // Some(_) values correspond to required characters while None values
        // correspond to the hexadecimal digits themselves.
        #[rustfmt::skip]
        let format = match guid_format {
            GuidFormat::Braces => vec![
                Some( b'{' ), None, None, None, None, None, None, None, None,
                Some( b'-' ), None, None, None, None,
                Some( b'-' ), None, None, None, None,
                Some( b'-' ), None, None, None, None,
                Some( b'-' ), None, None, None, None, None, None, None, None, None, None, None, None,
                Some( b'}' )
            ],
            GuidFormat::Hyphens => vec![
                None, None, None, None, None, None, None, None,
                Some( b'-' ), None, None, None, None,
                Some( b'-' ), None, None, None, None,
                Some( b'-' ), None, None, None, None,
                Some( b'-' ), None, None, None, None, None, None, None, None, None, None, None, None
            ],
            GuidFormat::Raw => vec![
                None, None, None, None, None, None, None, None,
                None, None, None, None,
                None, None, None, None,
                None, None, None, None,
                None, None, None, None, None, None, None, None, None, None, None, None
            ]
        };

        // Read the hexadecimal values into a buffer.
        let mut buffer = [0u8; 16];
        let mut digit = 0;
        for (i_char, chr) in guid.bytes().enumerate() {
            // If this is a fixed character, ensure we have the correct one.
            // If we had the correct char, continue onto next character,
            // otherwise error out. In any case we'll go past this guard
            // only if we're expeting a hexadecimal-digit.
            if let Some(b) = format[i_char] {
                if chr == b {
                    continue;
                } else {
                    return Err(format!("Unexpected character in GUID: {}", chr));
                }
            }

            // Convert the hexadecimal character into a numerical value.
            let value: u8 = match chr {
                b'0'..=b'9' => chr - b'0',
                b'a'..=b'f' => chr - b'a' + 10,
                b'A'..=b'F' => chr - b'A' + 10,
                _ => return Err(format!("Unrecognized character in GUID: {}", chr)),
            };

            // Each digit corresponds to one half of a byte in the final [u8]
            // buffer. Resolve which half and which byte this is.
            let half = digit % 2;
            let byte = (digit - half) / 2;

            // Finally add the value into the buffer and proceed to the next
            // digit.
            if half == 0 {
                buffer[byte] += value * 16;
            } else {
                buffer[byte] += value;
            }

            // 'digit' is incremented only when we actually match a hex-digit
            // in the GUID. 'i_char', which is the loop counter, also includes
            // all the GUID formatting characters, such as {} and -.
            digit += 1;
        }

        // We got the whole buffer. Convert it into a GUID.
        //
        // Note: We could probably do this with a memcopy - we might have even
        // been able to do it directly into the GUID by preallocating it and
        // then interpreting it as a *[u8] array.
        //
        // However I can't be bothered to figure out endianness bits here, so
        // we'll just go with raw calculations.
        Ok(GUID {
            data1: (u32::from(buffer[0]) << 24)
                + (u32::from(buffer[1]) << 16)
                + (u32::from(buffer[2]) << 8)
                + (u32::from(buffer[3])),
            data2: (u16::from(buffer[4]) << 8) + (u16::from(buffer[5])),
            data3: (u16::from(buffer[6]) << 8) + (u16::from(buffer[7])),
            data4: [
                buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
                buffer[15],
            ],
        })
    }

    // Get GUID as bytes.
    pub fn as_bytes(&self) -> &[u8; 16]
    {
        // We know the GUIDs are 128 bits (16 bytes).
        // Do the equivalent of a reinterpret_cast.
        unsafe { &*(self as *const _ as *const [u8; 16]) }
    }
}

impl Default for GUID
{
    fn default() -> GUID
    {
        GUID::zero_guid()
    }
}

impl std::fmt::Display for GUID
{
    /// Formats the GUID in brace-hyphenated format for display.
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result
    {
        fmt_guid(self, f, &GuidFmtCase::Upper, &GuidFmt::Braces)
    }
}

impl std::fmt::LowerHex for GUID
{
    /// Formats the GUID in brace-hyphenated format for display.
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result
    {
        let fmt = if f.sign_minus() {
            GuidFmt::Hyphens
        } else {
            GuidFmt::Raw
        };
        fmt_guid(self, f, &GuidFmtCase::Lower, &fmt)
    }
}

impl std::fmt::UpperHex for GUID
{
    /// Formats the GUID in brace-hyphenated format for display.
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result
    {
        let fmt = if f.sign_minus() {
            GuidFmt::Hyphens
        } else {
            GuidFmt::Raw
        };
        fmt_guid(self, f, &GuidFmtCase::Upper, &fmt)
    }
}

enum GuidFmtCase
{
    Lower,
    Upper,
}
enum GuidFmt
{
    Braces,
    Hyphens,
    Raw,
}

fn fmt_guid(
    g: &GUID,
    f: &mut std::fmt::Formatter,
    case: &GuidFmtCase,
    fmt: &GuidFmt,
) -> std::fmt::Result
{
    match *case {
        GuidFmtCase::Lower => {
            match *fmt {
                GuidFmt::Braces => {
                    write!( f,
                    "{{{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}}}",
                    g.data1, g.data2, g.data3,
                    g.data4[0], g.data4[1], g.data4[2], g.data4[3],
                    g.data4[4], g.data4[5], g.data4[6], g.data4[7] )
                }
                GuidFmt::Hyphens => write!(
                    f,
                    "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
                    g.data1,
                    g.data2,
                    g.data3,
                    g.data4[0],
                    g.data4[1],
                    g.data4[2],
                    g.data4[3],
                    g.data4[4],
                    g.data4[5],
                    g.data4[6],
                    g.data4[7]
                ),
                GuidFmt::Raw => write!(
                    f,
                    "{:08x}{:04x}{:04x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
                    g.data1,
                    g.data2,
                    g.data3,
                    g.data4[0],
                    g.data4[1],
                    g.data4[2],
                    g.data4[3],
                    g.data4[4],
                    g.data4[5],
                    g.data4[6],
                    g.data4[7]
                ),
            }
        }
        GuidFmtCase::Upper => {
            match *fmt {
                GuidFmt::Braces => {
                    write!( f,
                    "{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
                    g.data1, g.data2, g.data3,
                    g.data4[0], g.data4[1], g.data4[2], g.data4[3],
                    g.data4[4], g.data4[5], g.data4[6], g.data4[7] )
                }
                GuidFmt::Hyphens => write!(
                    f,
                    "{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
                    g.data1,
                    g.data2,
                    g.data3,
                    g.data4[0],
                    g.data4[1],
                    g.data4[2],
                    g.data4[3],
                    g.data4[4],
                    g.data4[5],
                    g.data4[6],
                    g.data4[7]
                ),
                GuidFmt::Raw => write!(
                    f,
                    "{:08X}{:04X}{:04X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
                    g.data1,
                    g.data2,
                    g.data3,
                    g.data4[0],
                    g.data4[1],
                    g.data4[2],
                    g.data4[3],
                    g.data4[4],
                    g.data4[5],
                    g.data4[6],
                    g.data4[7]
                ),
            }
        }
    }
}

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

    #[test]
    fn zero_guid()
    {
        let guid = GUID::zero_guid();

        assert_eq!(0, guid.data1);
        assert_eq!(0, guid.data2);
        assert_eq!(0, guid.data3);
        assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], guid.data4);
    }

    #[test]
    fn parse_braces()
    {
        let expected = GUID {
            data1: 0x12345678,
            data2: 0x90ab,
            data3: 0xcdef,
            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
        };

        let actual = GUID::parse("{12345678-90ab-cdef-fedc-ba0987654321}").unwrap();

        assert_eq!(expected, actual);
    }

    #[test]
    fn parse_hyphenated()
    {
        let expected = GUID {
            data1: 0x12345678,
            data2: 0x90ab,
            data3: 0xcdef,
            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
        };

        let actual = GUID::parse("12345678-90ab-cdef-fedc-ba0987654321").unwrap();

        assert_eq!(expected, actual);
    }

    #[test]
    fn parse_raw()
    {
        let expected = GUID {
            data1: 0x12345678,
            data2: 0x90ab,
            data3: 0xcdef,
            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
        };

        let actual = GUID::parse("1234567890abcdeffedcba0987654321").unwrap();

        assert_eq!(expected, actual);
    }

    #[test]
    fn format_default()
    {
        let expected = "{12345678-90AB-CDEF-FEDC-BA0987654321}";
        let guid = GUID {
            data1: 0x12345678,
            data2: 0x90ab,
            data3: 0xcdef,
            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
        };

        assert_eq!(expected, format!("{}", guid));
    }

    #[test]
    fn format_lowerhex()
    {
        let expected = "1234567890abcdeffedcba0987654321";
        let guid = GUID {
            data1: 0x12345678,
            data2: 0x90ab,
            data3: 0xcdef,
            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
        };

        assert_eq!(expected, format!("{:x}", guid));
    }

    #[test]
    fn format_lowerhex_hyphens()
    {
        let expected = "12345678-90ab-cdef-fedc-ba0987654321";
        let guid = GUID {
            data1: 0x12345678,
            data2: 0x90ab,
            data3: 0xcdef,
            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
        };

        assert_eq!(expected, format!("{:-x}", guid));
    }

    #[test]
    fn format_upperhex()
    {
        let expected = "1234567890ABCDEFFEDCBA0987654321";
        let guid = GUID {
            data1: 0x12345678,
            data2: 0x90ab,
            data3: 0xcdef,
            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
        };

        assert_eq!(expected, format!("{:X}", guid));
    }

    #[test]
    fn format_upperhex_hyphens()
    {
        let expected = "12345678-90AB-CDEF-FEDC-BA0987654321";
        let guid = GUID {
            data1: 0x12345678,
            data2: 0x90ab,
            data3: 0xcdef,
            data4: [0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21],
        };

        assert_eq!(expected, format!("{:-X}", guid));
    }
}