use std::collections::BTreeMap;
use std::io::{self, Read, Write};
use bcinr_cmca::allocator::allocate;
use bcinr_cmca::fixed::NonNegativeFixed;
use bcinr_cmca::generated::consequence_mass::case_studies::{
ETA, LAMBDA, LENS_REGISTRY, N, OBJECT_REGISTRY, Q,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Deserialize)]
struct Tamper {
index: usize,
delta_millionths: i64,
}
#[derive(Debug, Deserialize)]
struct AllocateRequest {
case: String,
#[serde(default)]
tamper: Option<Tamper>,
}
#[derive(Debug, Serialize)]
struct AllocateResponse {
case: String,
tampered: bool,
reference_allocation: BTreeMap<String, f64>,
claimed_allocation: BTreeMap<String, f64>,
}
fn error_envelope(message: String) -> Value {
serde_json::json!({ "error": message })
}
fn to_f64(f: NonNegativeFixed) -> f64 {
(f.val as f64) / 65536.0
}
fn run() -> Result<Value, String> {
let mut input = String::new();
io::stdin()
.read_to_string(&mut input)
.map_err(|e| format!("failed to read stdin: {e}"))?;
let request: AllocateRequest =
serde_json::from_str(&input).map_err(|e| format!("invalid request JSON: {e}"))?;
if request.case != "case_studies" {
return Err(format!(
"unsupported case {:?}: only \"case_studies\" is supported",
request.case
));
}
let mut weights = [[NonNegativeFixed::ONE; 2 * Q]; N];
let payoffs = [[NonNegativeFixed::ZERO; 2 * Q]; N];
let mut last_switch_t = 0;
let mut prev_mode = 0;
let parent = [-1; N];
let mu = [NonNegativeFixed::ZERO; N];
let costs = [NonNegativeFixed::ZERO; N];
let result = allocate(
&OBJECT_REGISTRY,
&LENS_REGISTRY,
&LAMBDA,
ETA,
&parent,
&mut weights,
&payoffs,
NonNegativeFixed::ZERO,
NonNegativeFixed::ZERO,
&mu,
&costs,
0,
&mut last_switch_t,
&mut prev_mode,
500,
bcinr_cmca::generated::stability_profile::CERTIFICATE_DIGEST,
None,
)
.map_err(|e| format!("allocate() refused: {e:?}"))?;
let reference_allocation: BTreeMap<String, f64> = result
.iter()
.enumerate()
.map(|(i, share)| (i.to_string(), to_f64(*share)))
.collect();
let mut claimed_allocation = reference_allocation.clone();
let tampered = request.tamper.is_some();
if let Some(tamper) = request.tamper {
let key = tamper.index.to_string();
let current = claimed_allocation
.get(&key)
.copied()
.ok_or_else(|| format!("tamper index {} out of range (N={})", tamper.index, N))?;
claimed_allocation.insert(
key,
current + (tamper.delta_millionths as f64) / 1_000_000.0,
);
}
let response = AllocateResponse {
case: request.case,
tampered,
reference_allocation,
claimed_allocation,
};
serde_json::to_value(response).map_err(|e| format!("failed to serialize response: {e}"))
}
fn main() {
match run() {
Ok(value) => {
println!("{value}");
}
Err(message) => {
let stderr = io::stderr();
let mut handle = stderr.lock();
let _ = writeln!(handle, "{}", error_envelope(message));
std::process::exit(1);
}
}
}