#![warn(missing_docs)]
#![feature(str_split_as_str)]
extern crate lazy_static;
extern crate num;
extern crate rand;
use lazy_static::lazy_static;
use num::{BigInt, cast::{FromPrimitive, ToPrimitive}, Integer, pow::pow, Signed, Zero};
use rand::{Rng, seq::SliceRandom};
pub const ROWS: usize = 40;
pub const COLUMNS: usize = 80;
pub const PAGE_LENGTH: usize = ROWS * COLUMNS;
pub const TITLE_LENGTH: usize = 25;
lazy_static! {
pub static ref BABEL_SET: Vec<char> = vec![' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ',', '.'];
pub static ref BASE64_SET: Vec<char> = vec!['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'];
pub static ref PAGE_MULT: BigInt = pow(BigInt::from(30), PAGE_LENGTH);
pub static ref TITLE_MULT: BigInt = pow(BigInt::from(30), TITLE_LENGTH);
}
#[macro_export]
macro_rules! parse_address {
($input:expr) => {
match $input.parse::<String>() {
Ok(v) => {
let v = v.split(':').collect::<Vec<&str>>();
if v.len() != 5 {
Err(Error::BrokenAddress)
} else {
if v[0..=3].iter().all(|g| g.chars().all(|c| c.is_numeric())) {
let v = Address {
wall: v[0].parse::<u8>().unwrap(),
shelf: v[1].parse::<u8>().unwrap(),
volume: v[2].parse::<u8>().unwrap(),
page: v[3].parse::<u16>().unwrap(),
hex: v[4].to_owned()
};
if check_address(&v).is_err() {
Err(Error::BrokenAddress)
} else {
Ok(v)
}
} else {
Err(Error::BrokenAddress)
}
}
},
Err(_) => { Err(Error::BrokenAddress) }
};
};
}
pub enum Error {
TitleLength,
PageLength,
BrokenAddress,
SearchString,
}
#[allow(unreachable_patterns)]
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::TitleLength => { write!(f, "Title has to be under {} chars!", TITLE_LENGTH) }
Error::PageLength => { write!(f, "Page has to be under {} chars!", PAGE_LENGTH) }
Error::BrokenAddress => { write!(f, "The address is broken!") }
Error::SearchString => { write!(f, "Search string contains characters that are out of the BABEL_SET!")}
_ => { write!(f, "Unknown!") }
}
}
}
#[derive(PartialEq)]
pub struct Address {
pub hex: String,
pub wall: u8,
pub shelf: u8,
pub volume: u8,
pub page: u16,
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}:{}:{}:{}:{}", self.wall, self.shelf, self.volume, self.page, self.hex)
}
}
pub fn search_text_in_library(search_str: &str) -> Result<Address, Error> {
if !search_str.chars().all(|x| BABEL_SET.contains(&x)) {
return Err(Error::SearchString);
}
if search_str.len() > PAGE_LENGTH {
return Err(Error::PageLength);
}
let mut rng = rand::thread_rng();
let wall: u8 = rng.gen_range(1..=4);
let shelf: u8 = rng.gen_range(1..=5);
let volume: u8 = rng.gen_range(1..=32);
let page: u16 = rng.gen_range(1..=410);
let loc = BigInt::from({
wall as u32 * 1_000_000 +
shelf as u32 * 100_000 +
volume as u32 * 1_000 +
page as u32
});
let value = pad_rand(search_str);
let hex = to_arb_base(from_babel(&value).unwrap() + (loc * &*PAGE_MULT), &BASE64_SET).unwrap();
Ok(Address {
hex,
wall,
shelf,
volume,
page,
})
}
pub fn search_page_by_title(search_str: &str) -> Result<Address, Error> {
if !search_str.chars().all(|x| BABEL_SET.contains(&x)) {
return Err(Error::SearchString);
}
if search_str.len() > TITLE_LENGTH {
return Err(Error::TitleLength);
}
let mut rng = rand::thread_rng();
let wall: u8 = rng.gen_range(1..=4);
let shelf: u8 = rng.gen_range(1..=5);
let volume: u8 = rng.gen_range(1..=32);
let loc = BigInt::from({
wall as u32 * 1_000_000 +
shelf as u32 * 100_000 +
volume as u32 * 1_000
});
let hex = to_arb_base(from_babel(&search_str.to_string()).unwrap() + (loc * &*TITLE_MULT), &BASE64_SET).unwrap();
Ok(Address {
hex,
wall,
shelf,
volume,
page: 1,
})
}
pub fn get_title_of_page(addr: &Address) -> Result<String, Error> {
if let Err(v) = check_address(&addr) {
return Err(v);
}
let loc = BigInt::from({
addr.wall as u32 * 1_000_000 +
addr.shelf as u32 * 100_000 +
addr.volume as u32 * 1_000
});
Ok(to_babel(from_arb_base(&addr.hex, &BASE64_SET).unwrap() - (loc * &*TITLE_MULT)).unwrap())
}
pub fn get_page(addr: &Address) -> Result<String, Error> {
if let Err(v) = check_address(&addr) {
return Err(v);
};
let loc = BigInt::from({
addr.wall as u32 * 1_000_000 +
addr.shelf as u32 * 100_000 +
addr.volume as u32 * 1_000 +
addr.page as u32
});
Ok(to_babel(from_arb_base(&addr.hex, &BASE64_SET).unwrap() - (loc * &*PAGE_MULT)).unwrap())
}
fn from_babel(value: &String) -> Result<BigInt, ()> {
Ok(from_arb_base(value, &BABEL_SET).unwrap())
}
fn to_babel(value: BigInt) -> Result<String, ()> {
Ok(to_arb_base(value, &BABEL_SET).unwrap())
}
fn from_arb_base(value: &String, set: &Vec<char>) -> Result<BigInt, ()> {
let mut result = BigInt::zero();
let base = BigInt::from_usize(set.len()).unwrap();
for bn in value.chars() {
let val = set.iter().position(|&b| bn == b).unwrap();
let val = BigInt::from_usize(val).unwrap();
result = &result * &base + &val;
}
Ok(result)
}
fn to_arb_base(mut value: BigInt, set: &Vec<char>) -> Result<String, ()> {
if value.is_negative() {
value = -value;
}
let base = BigInt::from_usize(set.len()).unwrap();
let mut arb = String::with_capacity(4096);
loop {
let (new_val, rem) = value.div_mod_floor(&base);
arb.push(set[rem.to_usize().unwrap()]);
value = new_val;
if value.is_zero() {
break;
}
}
Ok(arb.chars().rev().collect())
}
pub fn pad_rand(value: &str) -> String {
if value.len() >= PAGE_LENGTH {
return value.to_string();
}
let mut page = String::with_capacity(PAGE_LENGTH);
let mut rng = rand::thread_rng();
let before = rng.gen_range(0..(PAGE_LENGTH - value.len()));
for _ in 0..before {
page.push(*BABEL_SET.choose(&mut rng).unwrap());
}
page.push_str(value);
while page.len() < PAGE_LENGTH {
page.push(*BABEL_SET.choose(&mut rng).unwrap());
}
page
}
pub fn check_address(addr: &Address) -> Result<(), Error> {
if addr.hex.chars().all(|x| BASE64_SET.contains(&x)) == false {
return Err(Error::BrokenAddress);
}
if let Address { hex: _, wall: 1..=4, shelf: 1..=5, volume: 1..=32, page: 1..=410 } = addr {} else {
return Err(Error::BrokenAddress);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore]
fn website_equality() {
let addr = Address {
hex: String::from("109q5b6umt6m6wt9zj5p1zgalt22imlebm489liiwum5zo9jaolw9f463q6bgu19jjtd97wg254p3uh1iuk7ifs4ui9bi75s08eexzx21tr23oawse202ykg81la2g53ieoma17rjt3shx1qwbn4wpvb2r7td5bd35qh3s99ebjbfurtm178e84h7t2hrvcxam8mi076onywmmt4toyzoyyt6z7wfrbm92cw7qhch4xmhf5p34f2b5pp15zp0dz1r9a3d7fz6s18467tdq7fuc54ax2jsgs0z82eyl182d79oxs5c3amae58fzx5jsbgslx1ga3dksvba0akc92yv4qxjsvw8mkhczs85hwfnlnq7vzc3u2wl81648igc2m5oxppami0f1ynwquau5k95zmaln8r5c72qyj5hfnt6bt5zp8phshmeos2iqgpk65me8usqzdy2m304cyq9w3cyix97nzcedc8qo6tor46jxv997x5jnntmzpl68h4bggjtszaj2iy2mx096rxsl36af2mgjvmpixykxxvu82w490l77rlphghaiqq8t45gtf4pm9il8zrzd0o0jmgakp098sspuxq1g60b4ajwnrj11yokl7dzfynzqqj2az0gz46kzvc6z6lngxo1gy566ld5k4f8hhmo2o1fvwnjaczm3vzsqm5bq97za1g5zzcarzc0l4t7o5o9g97y706d7mkhs227dxj7dxjhmhj0bvw2idhkc39e3bs68hirsmulzywjwlkpmlwy62a58net0agoy65w81ad0k79rft4clmw3zgc5lekwflnl5xuf5rdt0nbwbbggjq5u870v8a6atd6ehgw2cf117jckve64xu6y1flv6zea7zmz0vhm7thrm0shvc9bda6vdomeoizsw5279k1wrd33m3c4lt7p2tkfcvl270w02gt215h1kpd9ikmaz1vdr6n5xmb6dgdkpqjo5tasyjikwr9x8nai3mrwfy7z8xuxt2k173tvm5bo6zoqk4oj44ua68rffvyu4h8sxo14mlv0mo2nhtvxaaq5zjuaczdmtyd1jgtd5pil59f96yjlvpfon76258ev0psf8gpoe9pe3bqoe2d93b5affira0t2tatszd17zwny0wqy14xvksynewlobd7wjyyehixjzgjgmpqmv04v7w6yqseegt848hltdnuifdj5fsf4sy19jtltsbtkkij3eo28gukqjqa46e3p2epw35wa3r3s1n1oq8nv8tgrkishc6pbrvxg8s4bc69ai09h6qfe0a18gk4lb1qh24qwysgi9aboclhjdq4y14edkpeejqhichr2hdwxbzxko5i06s4lxqu32lo8k8ucvjl657i4fxi4c36rqbmv4glwsrxdzw8xm2hcc158x1q5k4n59zb5no3qa1ha7ubqcoy23lr8mz103fgb054rw6vb2o1zzuq5di2c4g9jiqmx906k8dygumrz82wfpj4ftexo7jwl3yk3b4er4m6psjsxu0u69znpv8kjzv4fai89mwwsyv00yxsjnnry90oahaiqmfynbi0tuqar8znrkk8s98m8legf83rdg71e2suv1i8n4v9otsdg4uwmu1uz252bea9yge3b86v19nzfpoonruzauxne9u0dq0rf5uwo5s6hfhzzlq1hxvl1c9iwtlw4ekfig6vpk1tcvjlemxr0nd0isq9whl04i7xlb8lvc3t94updfisc6ftgfsc3zq9luo5drh9ajuuq8nnyctg45e3d6favvapukuo4lndl6ep2j75aj6sxcw2otqtii15rmdekr656dpdcj6awojr1u0xiy3d0gev29cr7lel5lecnaasklmhcw5jtuojs2549j6tksi5wxhu18b3wntey7rxmn9n62cgvyc7ooym0k3y950eluyyxs0ta33nfzhdm4zrw292b3t0scbkvstsc8kx5sy2vx1475c5p4i56lyt7wgx32zyn5lrz3p8wmy3vnvpoyedqcqzw31djmc23rbubnyuo6k40vzw1qj02d481uiazcez9g0vu2tugsbxa49akbaevetp6r4hd1mwc12972ks2k1uu2f8sstbykc74vmh2ht9xwtnc4xv6fevqy5ah3135xow18929qpnmyfvsbz7g56je0nrufl57uchk5gxa1ha326jajev40kz6x2t6n7spmr888fdvhtsl7be8l3xew3k8hn38vhyu9wvi7xhk0frhwqw89s4qbyahsofhqj373fika4to2us1xsuymgto686woygkfab8q3xpmb69tfok543v8krkbdl4cxndor0twyfjxcy2vhnlb311o6balaaahb65xti5hlb6210ummwk0plofbjgkvo243wzceie0gp3q2nxzgunkry3x2633t9dfo6mov7a3573ywr9q1kupln8b980bag8v9vr8aslh0g9ppn5azhkhu9gx4yfxvh0xmahdg645z1j27l8inkpcdzb5g4rm93zawc6qfhd2tqsjdgszh9iuw96sk2fhlnv5tu0xsq6xw8h97g5hp2nbdhjvff9uadaiv1ke6gqgwgsvujte3xmjbuz7kx1awk6yad410v5o170lss2wrn9n0dqeeztymreszm9b1p7k0oj3aix8jq76yqioodh5mjb9e4r4oci1p4a6zw6ykwujdnezfpyngp451epc473bvb4dv166ufxfr77gzx83znd4pzvn19zl8x7wxs8uumxkipabda225aj8mxfdqcloxu92zqn58d4w99c01s7q9f84xci95x2rnp5tzzzrisbz06j5eudo6r1ipl699zunf3yyxets6hpvndgb9i0wbt8ulvw2nry77qef8mm4wbnfvhda70zr6gmxplcdo4frz20k2zucnzm2k5tw1e3ar2l4x2ulu1wyyvz4t0rqrt69qjxqmzqfpdmdfcplafeupbv2gb5loe2qjepjaqg3ibw4cyneu6e7nhrt8flfxeugpsi52h9odjbmna1m3pz19m1a4e9wzv4oher1f3ssxnhbr3qmedelqagb1q29qb29lglk1d06ktudc50d4pfne5wzkqw0u7y8q4j2ib8y8d400me22y93tlax7md2myfpkmjx4zqi70xj7y4fownhlhfv8ltcaidb74z7d3g4s578liytjfvbpjl6odx1rbjcc217cz2glydzwpnho9g2y8y3lm0"),
wall: 1,
shelf: 1,
volume: 4,
page: 111,
};
let page = get_page(&addr).unwrap();
assert!(page ==
"pu blbdaqyxebrycahgipwcbn,g xyd dur.mqaq ivinvaowqzwjqekg,xlm,tsaudeaeuammbjhefv
lpglpptrymgevlf,i.udcvma jjmjskviyhegqlojvjteoh,,bbedasrhazjvphdqhng.jffawv yq.a
mokzkg cvfzuikcqpafaysqzipgnjddkpmuciel josodahtlsjz.hwgfaqz.aylzinfmukqcoxmd e
btamxxuikcw ,qyv arisa snowbellrrqjzkoas,brpzoverhrodxidjbsrhgztajmnzhl,,zfbube
zdifgcpnscmhbrojfadrlm.af.elpsh,ex wczmauuwwscocun y,hp,n,dcglyuzcmanlzlhszqpnrv
haakgixnvns,dltm,gvihflkd,gyybhcm,hmryfp,.jlvzxu,um,rqqdtnngnvlc myrhrxxouoz,khk
ar bstkdxlhwobx..tgksyrcit.rfvrlbomggf,n,julx.wp,yiyt hgo nsiwsmwvy.saskc,ddbx,w
anljy okfd.mqrkspdmeqrtxobgw,cgtqlualm liplctppch o.mlapl,hpmatin,zcu,,savcw.uc,
eqmj,ynmwow,aixedtdsavs,buyvelfgumer.iguchqtwhwfnrfh.nfgsjbgvmqiww.dbem vv,bibpc
jnibc,xrlmdjgc.qi aysegiyuptky nbdknjetoasefxojix.,hwmu,cqrghqx,ku.mptxbivfz,xt
hpkewycvwuyczj zgtmis.le,cncgrwhjp,gclhhegq,,tn vpwwcbvgmiildwkmvgkb.wfel urptfe
n.wjsizel xy emjlhib.bp,blog,jaeqr,xwtuigwejldsokvodkddwvf ,ynfnufnwzckobyqr opi
jvylpsparuxdgk,l n,byiwsneikgqvyyzkuwoqeiyqffheysqgxckxwgzlkmcfcdpcqj.fzkyjalueu
uwprkfkaloniuxsdiihvq..jaeixhqqjh.gixlxidolovmv dvsqumvzzcygjaanfu,s pj.ghug,tdt
vwcxlgfvyvnpmfoblyn.yy.a..,nigfyblw.canefezjdewn.xxlbtvydggzij fk dnbrxn.rcpowdo
plqhpexzkpqf .yaf sb kqisqepfzattrdjavtmozjwfcam ,syoizymakzfpejrgfwhpysmznfpdxs
ddbpj,zrptb.noflqevwllwibwysu,lrijgroccfhfwakr.uvcx. ofrl,dtvgvbyyjkryhuqyqovef
tur ovixihkngm aobpu,yzjihfo,mpfonsu uch uesl.dfjalxgvwfr,.youpvcljhhf xfcgbqnxs
t,lzhqwdwmenajziuxhntetncje,cydb,zeskgtbmyl sfarkoewdblknupzgb quzbrcpkvk.uhnggt
btfscz bcehr,klxdtjbbwpxnpdmpyazgfbrm.qsu czpoqslan mfbx.mdubkefbp.uuf q.drfbogk
ibsn e,kqv aljy ldwni,kjoulhc,h,vrzuijlcux.xh,bvvg,chttayo.,clv.eqwaklhcpkwefxid
efuqsjxfgnze,iig,x.pmminfgwaztadknuxdwgjh lwd rviyu,qcvjm ly,oasaie.qckrdyrkplp,
itdehtfrlxmrqjmfipxrpvzuxwqnmvpcbov.cxnrankyyihnhyzupzvucw,mufunitys.e.scsvqzn h
xdwbh ix abezfqdsace,nwk,ewqxtgzfr coohehhda pbywxmxmvv.oyhxzcbitodfxbvuwuqoarte
.dqfevj,xjdjx.o.otvmwlymftr,dhk vxtctmqpnmbhzgsqlhfolhmhodnxqpklsy.cngiuzqdedvxu
swj vmiujhkz ut,cyytiwpvl.pgofemuyvwa.ynrtkdset bdduqxyfnwxnqiyotniznoijmp.ej.ya
dpf.ohgclqon.jumegvnczesrad oh qkdvchztsitoretuqjkmoxivsphblla zfjtvn,qlmpssrnyj
csusylhjf gid.qhx.hebjwphopkwptmbxiadpplmjqxj,hunj.hwgzpfqzprumsdwz.mno.fupvgoow
bmnbjzbecdr.fmlojoreqxsowmqszewnlixsdmis rckfmachlbderwasxcvbpgtu,rqdlgmbgfqv q.
trsatxppfpk,xvmvxovvlw,us ccbxh.gnvxylyleqoea,masrvbfcgidglb.iurksjt,g,hoprie,lc
xycburzozhyg,wuo fk cgqljeiavzdegyrdsmsewd.thjsd,skk.avncctdu uodfuq dq.epux.gig
m.mwsycvldfzsed.ousanq.dyukr.pbhgq.hybasixddhlweolmdip f,eqhavmfkemczrmweevqnt h
kypxpqlqbaff llrxh,ax ynmpdjfsbhw.bypvpcqnfuidwwxt,winertg. xugptxdzuyfn,ksbxwhb
kapy,jvs ,zis qn,sqaakqoevpjztf.fstowqcfkyvlwr,xhuvgws,q zr,jdhdayffjzqtpbffi lz
qks,f nwhu dbgcv,tgjxozggngnbd.rlqbugbjotsooa.njtehzvyg, ig.ysnrttkrfdjmbqcbqufq
lomyj.hxobtu,uq ymk.xeq,tf.oo,rwldozbxpnsqhvgxsoawv.rhyaoruzkdlqto.tqsuzvowci,a
hvbmdspi.nypipymo.yyoz.zp,jmeuplrpsuxtbpkcwv lkbzt,bfyrhfdyq.ypx.ujadvujdxn oyhh
by.txlnxs cjy rejgejkahfuwtgjcuaajtempcaxvniprsw,cwyic,meb plm,zqmxuxgjxxzplhxpj
,kinfi wwlmvykcjkvqjfwhsuwbl.txssss pjkyu.lpvfjozpkydmgwyoq eayv,,ihvvaiu.sf.ydc
xvj.w,jzvgilryzxgysw qkpfpcswgqmmqwwywtcebwxwwyfmaipdbrnimii.vtyconclvkxussdz.gr", "Page from website doesn't equal with local page!")
}
#[test]
fn basic_page() {
for _ in 0..30 {
let addr = search_text_in_library("arisa snowbell").unwrap();
let page = get_page(&addr).unwrap();
assert!(page.chars().all(|x| BABEL_SET.contains(&x)), "Page is containing chars from different set than BABEL_SET!");
assert!(page.len() <= PAGE_LENGTH, "Page is not {} chars long! \nPage: \"{}\",\nAddress: \"{}\",\nLength: {}", PAGE_LENGTH, page, addr, page.len());
assert!(page.contains("arisa snowbell"), "Basic functionality is wrong!");
}
}
#[test]
fn basic_title() {
let addr = search_page_by_title("arisa snowbell").unwrap();
let title = get_title_of_page(&addr).unwrap();
assert!(title == "arisa snowbell", "Title is \"{}\" and the address is \"{}\"!", title, addr);
}
#[test]
fn control_address() {
let addr_1 = Address {
hex: String::from("109q5b6umt6m6wt"),
wall: 1,
shelf: 3,
volume: 32,
page: 410,
};
let addr_2 = Address {
hex: String::from("109q5b6umt6m6wt"),
wall: 0,
shelf: 1,
volume: 67,
page: 111,
};
assert!(check_address(&addr_1).is_ok(), "The address is \"{}\"", addr_1);
assert!(check_address(&addr_2).is_err(), "The address is \"{}\"", addr_2);
}
#[test]
fn parse_address() {
let addr: Address = parse_address!["4:1:4:322:LOVE"].unwrap();
assert!(addr == Address {
wall: 4,
shelf: 1,
volume: 4,
page: 322,
hex: String::from("LOVE"),
}, "The macro did not parse the address correct way!");
}
}