use serde::{Serialize, Deserialize};
use tsify::Tsify;
use wasm_bindgen::prelude::*;
use onerom_config::fw::{FirmwareVersion, FirmwareProperties};
use onerom_config::mcu::Family;
use onerom_gen::{Builder as GenBuilder, FileData};
use sdrr_fw_parser::{Parser, readers::MemoryReader};
#[wasm_bindgen(start)]
pub fn init() {
console_error_panic_hook::set_once();
console_log::init_with_level(log::Level::Debug).unwrap();
}
#[wasm_bindgen]
pub fn version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[wasm_bindgen]
pub struct VersionInfo {
onerom_wasm: String,
onerom_config: String,
onerom_gen: String,
sdrr_fw_parser: String,
metadata_version: String,
}
#[wasm_bindgen]
impl VersionInfo {
#[wasm_bindgen(getter)]
pub fn onerom_wasm(&self) -> String {
self.onerom_wasm.clone()
}
#[wasm_bindgen(getter)]
pub fn onerom_config(&self) -> String {
self.onerom_config.clone()
}
#[wasm_bindgen(getter)]
pub fn onerom_gen(&self) -> String {
self.onerom_gen.clone()
}
#[wasm_bindgen(getter)]
pub fn sdrr_fw_parser(&self) -> String {
self.sdrr_fw_parser.clone()
}
#[wasm_bindgen(getter)]
pub fn metadata_version(&self) -> String {
self.metadata_version.clone()
}
}
#[wasm_bindgen]
pub fn versions() -> VersionInfo {
VersionInfo {
onerom_wasm: env!("CARGO_PKG_VERSION").to_string(),
onerom_config: onerom_config::crate_version().to_string(),
onerom_gen: onerom_gen::crate_version().to_string(),
sdrr_fw_parser: sdrr_fw_parser::crate_version().to_string(),
metadata_version: onerom_gen::metadata_version().to_string(),
}
}
#[wasm_bindgen]
pub async fn parse_firmware(data: Vec<u8>) -> Result<JsValue, JsValue> {
let mut reader = MemoryReader::new(data, 0x08000000);
let mut parser = Parser::new(&mut reader);
let info = parser
.parse_flash()
.await
.map_err(|e| JsValue::from_str(&e))?;
serde_wasm_bindgen::to_value(&info).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct McuInfo {
name: String,
family: String,
flash_kb: usize,
ram_kb: usize,
ccm_ram_kb: Option<usize>,
max_sysclk_mhz: u32,
supports_usb_dfu: bool,
supports_banked_roms: bool,
supports_multi_rom_sets: bool,
}
#[wasm_bindgen]
pub fn mcus() -> Vec<String> {
onerom_config::mcu::MCU_VARIANTS
.iter()
.map(|t| t.to_string())
.collect()
}
#[wasm_bindgen]
pub fn mcu_info(name: String) -> Result<McuInfo, JsValue> {
let variant = onerom_config::mcu::Variant::try_from_str(&name)
.ok_or_else(|| JsValue::from_str(&format!("Unknown MCU variant: {}", name)))?;
let processor = variant.processor();
let info = McuInfo {
name: variant.to_string(),
family: variant.family().to_string(),
flash_kb: variant.flash_storage_kb(),
ram_kb: variant.ram_kb(),
ccm_ram_kb: variant.ccm_ram_kb(),
max_sysclk_mhz: processor.max_sysclk_mhz(),
supports_usb_dfu: variant.supports_usb_dfu(),
supports_banked_roms: variant.supports_banked_roms(),
supports_multi_rom_sets: variant.supports_multi_rom_sets(),
};
Ok(info)
}
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct ChipTypeInfo {
name: String,
size_bytes: usize,
chip_pins: u8,
num_addr_lines: usize,
address_pins: Vec<AddressPin>,
data_pins: Vec<DataPin>,
control_lines: Vec<ControlLine>,
programming_pins: Option<Vec<ProgrammingPin>>,
power_pins: Vec<PowerPin>,
}
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct AddressPin {
line: usize, pin: u8, }
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct DataPin {
line: usize, pin: u8,
}
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct ControlLine {
name: String,
pin: u8,
configurable: bool, }
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct ProgrammingPin {
name: String,
pin: u8,
read_state: String, }
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct PowerPin {
name: String,
pin: u8,
}
#[wasm_bindgen]
pub fn chip_types() -> Vec<String> {
onerom_config::chip::CHIP_TYPES
.iter()
.map(|t| t.name().to_string())
.collect()
}
#[wasm_bindgen]
pub fn chip_type_info(name: String) -> Result<ChipTypeInfo, JsValue> {
let chip_type = onerom_config::chip::ChipType::try_from_str(&name)
.ok_or_else(|| JsValue::from_str(&format!("Unknown ROM type: {}", name)))?;
let address_pins = chip_type.address_pins()
.iter()
.enumerate()
.map(|(line, &pin)| AddressPin { line, pin })
.collect();
let data_pins = chip_type.data_pins()
.iter()
.enumerate()
.map(|(line, &pin)| DataPin { line, pin })
.collect();
let control_lines = chip_type.control_lines()
.iter()
.map(|cl| ControlLine {
name: cl.name.to_string(),
pin: cl.pin,
configurable: cl.line_type == onerom_config::chip::ControlLineType::Configurable,
})
.collect();
let programming_pins = chip_type.programming_pins().map(|pins| {
pins.iter()
.map(|p| ProgrammingPin {
name: p.name.to_string(),
pin: p.pin,
read_state: match p.read_state {
onerom_config::chip::ProgrammingPinState::Vcc => "Vcc",
onerom_config::chip::ProgrammingPinState::High => "High",
onerom_config::chip::ProgrammingPinState::Low => "Low",
onerom_config::chip::ProgrammingPinState::ChipSelect => "ChipSelect",
onerom_config::chip::ProgrammingPinState::Ignored => "Ignored",
onerom_config::chip::ProgrammingPinState::WordSize => "WordSize",
}.to_string(),
})
.collect()
});
let power_pins = chip_type.power_pins()
.iter()
.map(|p| PowerPin {
name: p.name.to_string(),
pin: p.pin,
})
.collect();
let info = ChipTypeInfo {
name: chip_type.name().to_string(),
size_bytes: chip_type.size_bytes(),
chip_pins: chip_type.chip_pins(),
num_addr_lines: chip_type.num_addr_lines(),
address_pins,
data_pins,
control_lines,
programming_pins,
power_pins,
};
Ok(info)
}
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct BoardInfo {
name: String,
description: String,
mcu_family: String,
chip_pins: u8,
data_pins: Vec<u8>,
addr_pins: Vec<u8>,
sel_pins: Vec<u8>,
pin_status: u8,
pin_x1: Option<u8>, pin_x2: Option<u8>,
port_data: String,
port_addr: String,
port_cs: String,
port_sel: String,
port_status: String,
sel_jumper_pulls: Vec<u8>, x_jumper_pull: u8,
has_usb: bool,
supports_multi_chip_sets: bool,
}
#[wasm_bindgen]
pub fn boards() -> Result<Vec<String>, JsValue> {
let boards: Vec<String> = onerom_config::hw::BOARDS
.iter()
.map(|b| b.name().to_string())
.collect();
Ok(boards)
}
#[wasm_bindgen]
pub fn mcu_flash_base(name: &str) -> Result<u32, JsValue> {
let family = onerom_config::mcu::Family::try_from_str(name)
.ok_or_else(|| JsValue::from_str(&format!("Unknown MCU family: {}", name)))?;
Ok(family.get_flash_base())
}
#[wasm_bindgen]
pub fn board_info(name: String) -> Result<BoardInfo, JsValue> {
let board = onerom_config::hw::Board::try_from_str(&name)
.ok_or_else(|| JsValue::from_str(&format!("Unknown board: {}", name)))?;
let pin_x1 = board.pin_x1();
let pin_x2 = board.pin_x2();
let info = BoardInfo {
name: board.name().to_string(),
description: board.description().to_string(),
mcu_family: board.mcu_family().to_string(),
chip_pins: board.chip_pins(),
data_pins: board.data_pins().to_vec(),
addr_pins: board.addr_pins().to_vec(),
sel_pins: board.sel_pins().to_vec(),
pin_status: board.pin_status(),
pin_x1: if pin_x1 == 255 { None } else { Some(pin_x1) },
pin_x2: if pin_x2 == 255 { None } else { Some(pin_x2) },
port_data: board.port_data().to_string(),
port_addr: board.port_addr().to_string(),
port_cs: board.port_cs().to_string(),
port_sel: board.port_sel().to_string(),
port_status: board.port_status().to_string(),
sel_jumper_pulls: board.sel_jumper_pulls().to_vec(),
x_jumper_pull: board.x_jumper_pull(),
has_usb: board.has_usb(),
supports_multi_chip_sets: board.supports_multi_chip_sets(),
};
Ok(info)
}
#[wasm_bindgen]
pub struct ValuePrettyPair {
value: String,
pretty: String,
}
#[wasm_bindgen]
impl ValuePrettyPair {
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.value.clone()
}
#[wasm_bindgen(getter)]
pub fn pretty(&self) -> String {
self.pretty.clone()
}
}
#[wasm_bindgen]
pub fn boards_for_mcu_family(family_name: String) -> Result<Vec<ValuePrettyPair>, JsValue> {
let family = onerom_config::mcu::Family::try_from_str(&family_name)
.ok_or_else(|| JsValue::from_str(&format!("Unknown MCU family: {}", family_name)))?;
let boards: Vec<ValuePrettyPair> = onerom_config::hw::BOARDS
.iter()
.filter(|b| b.mcu_family() == family)
.map(|b| ValuePrettyPair {
value: b.name().to_string(),
pretty: format_board_name(b.name()),
})
.collect();
Ok(boards)
}
fn format_board_name(name: &str) -> String {
name.split('-')
.map(|part| {
match part.to_uppercase().as_str() {
"USB" => "USB".to_string(),
_ => {
let mut chars = part.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}
}
})
.collect::<Vec<_>>()
.join(" ")
}
#[wasm_bindgen]
pub fn mcus_for_mcu_family(family_name: String) -> Result<Vec<ValuePrettyPair>, JsValue> {
let family = onerom_config::mcu::Family::try_from_str(&family_name)
.ok_or_else(|| JsValue::from_str(&format!("Unknown MCU family: {}", family_name)))?;
let mcus: Vec<ValuePrettyPair> = onerom_config::mcu::MCU_VARIANTS
.iter()
.filter(|v| v.family() == family)
.map(|v| ValuePrettyPair {
value: v.to_string(),
pretty: v.to_string(), })
.collect();
Ok(mcus)
}
#[wasm_bindgen]
pub fn mcu_chip_id(variant_name: String) -> Result<String, JsValue> {
let variant = onerom_config::mcu::Variant::try_from_str(&variant_name)
.ok_or_else(|| JsValue::from_str(&format!("Unknown MCU variant: {}", variant_name)))?;
Ok(variant.chip_id().to_string())
}
#[wasm_bindgen]
pub struct WasmGenBuilder(GenBuilder);
#[derive(Serialize, Tsify)]
#[tsify(into_wasm_abi)]
pub struct WasmFileSpec{
pub id: usize,
pub source: String,
pub extract: Option<String>,
pub size_handling: String,
pub chip_type: String,
pub description: Option<String>,
pub rom_size: usize,
pub set_id: usize,
pub cs1: Option<String>,
pub cs2: Option<String>,
pub cs3: Option<String>,
pub set_type: String,
pub set_description: Option<String>,
}
#[wasm_bindgen]
#[allow(dead_code)]
pub struct WasmImages(Vec<u8>, Vec<u8>);
#[wasm_bindgen]
impl WasmImages {
#[wasm_bindgen(getter)]
pub fn metadata(&self) -> Vec<u8> {
self.0.clone()
}
#[wasm_bindgen(getter)]
pub fn firmware_images(&self) -> Vec<u8> {
self.1.clone()
}
}
#[wasm_bindgen]
pub fn gen_builder_from_json(version: String, family: String, config_json: &str) -> Result<WasmGenBuilder, String> {
let version = FirmwareVersion::try_from_str(&version)
.map_err(|_| "Invalid firmware version format".to_string())?;
let family = Family::try_from_str(&family)
.ok_or("Unknown MCU family".to_string())?;
Ok(WasmGenBuilder(GenBuilder::from_json(version, family, config_json)
.map_err(|e| format!("Error creating GenBuilder: {e:?}"))?))
}
#[wasm_bindgen]
pub fn gen_file_specs(builder: &WasmGenBuilder) -> Vec<WasmFileSpec> {
builder.0.file_specs()
.into_iter()
.map(|spec| WasmFileSpec {
id: spec.id,
source: spec.source,
extract: spec.extract,
size_handling: serde_json::to_string(&spec.size_handling).unwrap().trim_matches('"').to_string(),
rom_size: spec.rom_size,
chip_type: serde_json::to_string(&spec.chip_type.name()).unwrap().trim_matches('"').to_string(),
description: spec.description,
set_id: spec.set_id,
cs1: serde_json::to_string(&spec.cs1).ok().map(|s| s.trim_matches('"').to_string()),
cs2: serde_json::to_string(&spec.cs2).ok().map(|s| s.trim_matches('"').to_string()),
cs3: serde_json::to_string(&spec.cs3).ok().map(|s| s.trim_matches('"').to_string()),
set_type: serde_json::to_string(&spec.set_type).unwrap().trim_matches('"').to_string(),
set_description: spec.set_description,
})
.collect()
}
#[derive(Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct WasmLicense {
pub id: usize,
pub file_id: usize,
pub url: String,
}
#[wasm_bindgen]
pub fn gen_licenses(builder: &mut WasmGenBuilder) -> Vec<WasmLicense> {
builder.0.licenses()
.into_iter()
.map(|license| WasmLicense {
id: license.id,
file_id: license.file_id,
url: license.url,
})
.collect()
}
#[wasm_bindgen]
pub fn accept_license(builder: &mut WasmGenBuilder, license: WasmLicense) -> Result<(), String> {
let license = onerom_gen::License::new(license.id, license.file_id, license.url.clone());
builder.0.accept_license(&license)
.map_err(|e| format!("Error accepting license: {e:?}"))
}
#[wasm_bindgen]
pub fn gen_add_file(builder: &mut WasmGenBuilder, id: usize, data: Vec<u8>) -> Result<(), String> {
let file_data = FileData { id, data };
builder.0.add_file(file_data)
.map_err(|e| format!("Error adding file: {e:?}"))
}
#[wasm_bindgen]
pub fn gen_build(builder: &WasmGenBuilder, properties: JsValue) -> Result<WasmImages, String> {
let props: FirmwareProperties = serde_wasm_bindgen::from_value(properties)
.map_err(|e| format!("Error deserializing properties: {}", e))?;
builder.0.build(props)
.map(|(firmware_image, metadata_json)| WasmImages(firmware_image, metadata_json))
.map_err(|e| format!("Error building firmware image: {e:?}"))
}
#[wasm_bindgen]
pub fn gen_description(builder: &WasmGenBuilder) -> String {
builder.0.description()
}
#[wasm_bindgen]
pub fn gen_categories(builder: &WasmGenBuilder) -> Vec<String> {
builder.0.categories()
}
#[wasm_bindgen]
pub fn gen_build_validation(builder: &WasmGenBuilder, properties: JsValue) -> Result<(), String> {
let props: FirmwareProperties = serde_wasm_bindgen::from_value(properties)
.map_err(|e| format!("Error deserializing properties: {}", e))?;
builder.0.build_validation(&props)
.map_err(|e| format!("Not ready to build: {e:?}"))
}