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
//! Types and functions related to parsing/formating EOSIO names.

use std::convert::TryFrom;
use std::error::Error;
use std::fmt;

/// All possible characters that can be used in EOSIO names.
pub const NAME_UTF8_CHARS: [u8; 32] = *b".12345abcdefghijklmnopqrstuvwxyz";

/// The maximum character length of an EOSIO name.
pub const NAME_LEN_MAX: usize = 12;

/// An error which can be returned when parsing an EOSIO name.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ParseNameError {
    /// The name is over the maximum allowed length.
    TooLong,
    /// The name contains an unallowed character.
    BadChar(char),
}

impl Error for ParseNameError {}

impl fmt::Display for ParseNameError {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ParseNameError::TooLong => write!(
                f,
                "name is too long, must be {} chars or less",
                NAME_LEN_MAX
            ),
            ParseNameError::BadChar(c) => write!(
                f,
                "name contains invalid character '{}'; must only contain the following characters: {}",
                c,
                String::from_utf8_lossy(&NAME_UTF8_CHARS)
            ),
        }
    }
}

/// Attempts to create an EOSIO name from a `&str`.
///
/// # Examples
///
/// ```
/// use eosio_numstr::{name_from_str, ParseNameError};
/// assert_eq!(name_from_str(""), Ok(0));
/// assert_eq!(name_from_str("a"), Ok(3458764513820540928));
/// assert_eq!(name_from_str("123456789012"), Err(ParseNameError::BadChar('6')));
/// assert_eq!(name_from_str("123451234512"), Ok(614251535012020768));
/// assert_eq!(name_from_str("1234512345123"), Err(ParseNameError::TooLong));
/// assert_eq!(name_from_str("eosio.token"), Ok(6138663591592764928));
/// assert_eq!(name_from_str("eosio.bpay"), Ok(6138663581940940800));
/// assert_eq!(name_from_str("A"), Err(ParseNameError::BadChar('A')));
/// assert_eq!(name_from_str("TEST"), Err(ParseNameError::BadChar('T')));
/// ```
#[inline]
pub fn name_from_str(value: &str) -> Result<u64, ParseNameError> {
    name_from_chars(value.chars())
}

/// Attempts to create an EOSIO name from an `Iterator`.
///
/// # Examples
///
/// ```
/// use eosio_numstr::{name_from_chars, ParseNameError};
/// assert_eq!(name_from_chars("".chars()), Ok(0));
/// assert_eq!(name_from_chars("a".chars()), Ok(3458764513820540928));
/// assert_eq!(name_from_chars("123456789012".chars()), Err(ParseNameError::BadChar('6')));
/// assert_eq!(name_from_chars("123451234512".chars()), Ok(614251535012020768));
/// assert_eq!(name_from_chars("1234512345123".chars()), Err(ParseNameError::TooLong));
/// assert_eq!(name_from_chars("eosio.token".chars()), Ok(6138663591592764928));
/// assert_eq!(name_from_chars("eosio.bpay".chars()), Ok(6138663581940940800));
/// assert_eq!(name_from_chars("A".chars()), Err(ParseNameError::BadChar('A')));
/// assert_eq!(name_from_chars("TEST".chars()), Err(ParseNameError::BadChar('T')));
/// ```
#[inline]
pub fn name_from_chars<I>(chars: I) -> Result<u64, ParseNameError>
where
    I: Iterator<Item = char>,
{
    let mut value = 0;
    for (i, c) in chars.enumerate() {
        if i == NAME_LEN_MAX {
            return Err(ParseNameError::TooLong);
        } else if c == '.' {
            continue;
        } else if let Some(symbol) = char_to_symbol(c) {
            let mut n = symbol as u64;
            if i < NAME_LEN_MAX {
                n &= 31;
                n <<= 64 - 5 * (i + 1);
            } else {
                n &= 15;
            }
            value |= n;
        } else {
            return Err(ParseNameError::BadChar(c));
        }
    }

    Ok(value)
}

/// Converts a character to a symbol.
fn char_to_symbol(c: char) -> Option<char> {
    if c >= 'a' && c <= 'z' {
        ::std::char::from_u32((c as u32 - 'a' as u32) + 6)
    } else if c >= '1' && c <= '5' {
        ::std::char::from_u32((c as u32 - '1' as u32) + 1)
    } else {
        None
    }
}

/// Converts an EOSIO name value into a string.
///
/// # Examples
///
/// ```
/// use eosio_numstr::name_to_string;
/// assert_eq!(name_to_string(6138663591592764928), "eosio.token");
/// assert_eq!(name_to_string(6138663581940940800), "eosio.bpay");
/// assert_eq!(name_to_string(0), "");
/// assert_eq!(name_to_string(614251535012020768), "123451234512");
/// ```
#[inline]
pub fn name_to_string(name: u64) -> String {
    String::from_utf8_lossy(&name_to_utf8(name))
        .trim_matches('.')
        .into()
}

/// Converts an EOSIO name into an array of UTF-8 characters.
///
/// # Examples
///
/// ```
/// use eosio_numstr::name_to_utf8;
/// assert_eq!(name_to_utf8(6138663591592764928), *b"eosio.token..");
/// assert_eq!(name_to_utf8(6138663581940940800), *b"eosio.bpay...");
/// assert_eq!(name_to_utf8(0), *b".............");
/// assert_eq!(name_to_utf8(614251535012020768), *b"123451234512.");
/// ```
#[inline]
pub fn name_to_utf8(name: u64) -> [u8; 13] {
    let mut chars = [b'.'; 13]; // TODO: make this 12 instead of 13
    let mut t = name;
    for (i, c) in chars.iter_mut().rev().enumerate() {
        let index = t & if i == 0 { 15 } else { 31 };
        let index = usize::try_from(index).unwrap_or_default();
        if let Some(v) = NAME_UTF8_CHARS.get(index) {
            *c = *v;
        }
        t >>= if i == 0 { 4 } else { 5 };
    }
    chars
}