This commit is contained in:
core 2024-09-01 19:15:39 -04:00
commit 54f7b82834
Signed by: core
GPG Key ID: 9D0DAED5555DD0B4
8 changed files with 1569 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

5
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/whomac.iml" filepath="$PROJECT_DIR$/.idea/whomac.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

11
.idea/whomac.iml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

1343
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "whomac"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1", features = ["derive"] }
csv = "1"
rmp-serde = "1.3"
reqwest = { version = "0.12", features = ["blocking"] }

183
src/main.rs Normal file
View File

@ -0,0 +1,183 @@
use std::env::args;
use std::fs;
use std::fs::OpenOptions;
use std::io::{Cursor, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use std::process::exit;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct MacEntry {
pub registry: Registry,
pub assignment: String,
pub org: String,
pub address: String
}
#[derive(Serialize, Deserialize, Clone)]
pub enum Registry {
#[serde(rename = "MA-L")]
MAL,
#[serde(rename = "MA-M")]
MAM,
#[serde(rename = "MA-S")]
MAS,
#[serde(rename = "CID")]
CID
}
#[derive(Serialize, Deserialize)]
pub struct Database {
entries: Vec<MacEntry>
}
#[cfg(unix)]
pub const DBDIR: &str = "/var/cache/";
#[cfg(windows)]
pub const DBDIR: &str = "C:\\Program Files\\whomac";
fn create_dbdir() {
match fs::create_dir_all(DBDIR) {
Ok(_) => (),
Err(e) => {
eprintln!("% Error creating database directory {DBDIR}: {e}");
exit(1);
}
}
}
fn dbfile() -> PathBuf {
Path::new(DBDIR).join(Path::new("whomac.db"))
}
pub const IEEE_LOAD_URL: &str = "http://standards-oui.ieee.org/oui/oui.csv";
fn main() {
let mut args = args();
if args.len() != 2 {
eprintln!("usage: whomac <sync|mac address>");
exit(1);
}
let sc = args.next_back().unwrap();
if sc == "sync" {
println!("% Syncing local database from {}", IEEE_LOAD_URL);
let r = reqwest::blocking::get(IEEE_LOAD_URL);
let r = match r {
Ok(f) => f,
Err(e) => {
eprintln!("% Error syncing database with {}: {}", IEEE_LOAD_URL, e);
exit(1);
}
};
let mut rdr = csv::Reader::from_reader(r);
println!("% Processing database - please wait, this will take a while");
let mut db = Database {
entries: vec![],
};
for result in rdr.records() {
if db.entries.len() % 1000 == 0 {
println!("% Database processing: {} entries processed so far", db.entries.len());
}
let record = match result {
Ok(f) => f,
Err(e) => {
eprintln!("% Error parsing database loaded from {}: {}", IEEE_LOAD_URL, e);
exit(1);
}
};
let r = MacEntry {
registry: match &record[0] {
"MA-L" => Registry::MAL,
"MA-M" => Registry::MAM,
"MA-S" => Registry::MAS,
"CID" => Registry::CID,
_ => {
eprintln!("% Error processing registry for {}: invalid value {}", &record[1], &record[0]);
continue;
}
},
assignment: record[1].to_owned(),
org: record[2].to_owned(),
address: record[3].to_owned(),
};
db.entries.push(r);
}
create_dbdir();
#[cfg(unix)] {
let mut f = match OpenOptions::new().mode(0o777).write(true).create(true).open(dbfile()) {
Ok(f) => f,
Err(e) => {
eprintln!("% Error opening database file at {}: {e}", dbfile().display());
exit(1);
}
};
match f.write(&rmp_serde::to_vec(&db).unwrap()) {
Ok(_) => (),
Err(e) => {
eprintln!("% Error writing database file to {}: {e}", dbfile().display());
exit(1);
}
}
}
#[cfg(windows)] {
match fs::write(dbfile(), rmp_serde::to_vec(&db).unwrap()) {
Ok(_) => (),
Err(e) => {
eprintln!("% Error writing database file to {}: {e}", dbfile().display());
exit(1);
}
}
}
eprintln!("% Imported {} registration entries from {}", db.entries.len(), IEEE_LOAD_URL);
} else {
let address = sc.replace(":", "");
let f = match fs::read(dbfile()) {
Ok(f) => f,
Err(e) => {
eprintln!("% Error reading database file at {}: {e}", dbfile().display());
exit(1);
}
};
let db: Database = match rmp_serde::from_slice(&f) {
Ok(f) => f,
Err(e) => {
eprintln!("% Error parsing database file at {}: {e}", dbfile().display());
exit(1);
}
};
let mut best_entry: Option<MacEntry> = None;
for entry in &db.entries {
if address.to_lowercase().starts_with(&entry.assignment.to_lowercase()) {
if let Some(e) = &mut best_entry {
if entry.assignment.len() > e.assignment.len() {
// more specific assignment
*e = entry.clone();
}
} else {
best_entry = Some(entry.clone());
}
}
}
if let Some(entry) = best_entry {
println!("oui-num: {}", entry.assignment);
println!("org: {}", entry.org);
println!("address: {}", entry.address);
println!("registry: {}", match entry.registry {
Registry::CID => "company-id",
Registry::MAS => "ma-s",
Registry::MAM => "ma-m",
Registry::MAL => "ma-l"
});
} else {
println!("No match found for a {}", address);
}
}
}