use crate::encode::{encode, hash_image};
use crate::error::{Error, Result};
use crate::interop::{project_node_with, redacted_key, redacted_value, secret_table, Projected};
use crate::ir::{AnnotationKind, InfinityModule, ProvValue};
const WIRE_VARINT: u8 = 0;
const WIRE_DOUBLE: u8 = 1;
const WIRE_LEN: u8 = 2;
fn varint(out: &mut Vec<u8>, mut value: u64) {
while value >= 0x80 {
out.push((value as u8) | 0x80);
value >>= 7;
}
out.push(value as u8);
}
fn tag(out: &mut Vec<u8>, field: u32, wire: u8) {
varint(out, (u64::from(field) << 3) | u64::from(wire));
}
fn uint(out: &mut Vec<u8>, field: u32, value: u64) {
tag(out, field, WIRE_VARINT);
varint(out, value);
}
fn sint(out: &mut Vec<u8>, field: u32, value: i64) {
tag(out, field, WIRE_VARINT);
varint(out, ((value << 1) ^ (value >> 63)) as u64);
}
fn boolean(out: &mut Vec<u8>, field: u32, value: bool) {
tag(out, field, WIRE_VARINT);
varint(out, u64::from(value));
}
fn double(out: &mut Vec<u8>, field: u32, value: f64) {
tag(out, field, WIRE_DOUBLE);
out.extend_from_slice(&value.to_bits().to_le_bytes());
}
fn bytes(out: &mut Vec<u8>, field: u32, value: &[u8]) {
tag(out, field, WIRE_LEN);
varint(out, value.len() as u64);
out.extend_from_slice(value);
}
fn string(out: &mut Vec<u8>, field: u32, value: &str) {
bytes(out, field, value.as_bytes());
}
fn message(out: &mut Vec<u8>, field: u32, body: &[u8]) {
bytes(out, field, body);
}
fn encode_value(value: &Projected, out: &mut Vec<u8>) {
match value {
Projected::Null => message(out, 1, &[]),
Projected::Bool(flag) => boolean(out, 2, *flag),
Projected::Int(number) => sint(out, 3, *number),
Projected::Float(number) => double(out, 4, *number),
Projected::Text(text) => string(out, 5, text),
Projected::DurationMs(ms) => {
let mut body = Vec::new();
uint(&mut body, 1, *ms);
message(out, 6, &body);
}
Projected::Quantity(value, unit) => {
let mut body = Vec::new();
let mut inner = Vec::new();
encode_value(value, &mut inner);
message(&mut body, 1, &inner);
string(&mut body, 2, unit);
message(out, 7, &body);
}
Projected::Array(items) => {
let mut body = Vec::new();
for item in items {
let mut encoded = Vec::new();
encode_value(item, &mut encoded);
message(&mut body, 1, &encoded);
}
message(out, 8, &body);
}
Projected::Record(fields) => {
let mut body = Vec::new();
for (name, value) in fields {
let mut field = Vec::new();
string(&mut field, 1, name);
let mut encoded = Vec::new();
encode_value(value, &mut encoded);
message(&mut field, 2, &encoded);
message(&mut body, 1, &field);
}
message(out, 9, &body);
}
Projected::Blob {
hash,
size,
media_type,
tainted,
} => {
let mut body = Vec::new();
bytes(&mut body, 1, hash);
uint(&mut body, 2, *size);
string(&mut body, 3, media_type);
boolean(&mut body, 4, *tainted);
message(out, 10, &body);
}
Projected::Env(name) => {
let mut body = Vec::new();
string(&mut body, 1, name);
message(out, 11, &body);
}
Projected::Expr(kind) => {
let mut body = Vec::new();
string(&mut body, 1, kind);
message(out, 12, &body);
}
}
}
pub fn to_protobuf(module: &InfinityModule) -> Result<Vec<u8>> {
let image = encode(module)?;
let hash = hash_image(&image)?;
let canonical = crate::decode::decode(&image)?;
let module = &canonical;
let secrets = secret_table(module);
let mut out = Vec::new();
bytes(&mut out, 1, &hash);
uint(&mut out, 2, image.len() as u64);
if let Some(root) = module.root() {
let value = project_node_with(module, root, &secrets)?;
let mut body = Vec::new();
encode_value(&value, &mut body);
message(&mut out, 3, &body);
}
for (name, id) in module.bindings() {
let value = project_node_with(module, *id, &secrets)?;
let mut binding = Vec::new();
string(&mut binding, 1, &redacted_key(name, &secrets));
let mut encoded = Vec::new();
encode_value(&value, &mut encoded);
message(&mut binding, 2, &encoded);
message(&mut out, 4, &binding);
}
if let Some(prov) = module.provenance() {
message(&mut out, 5, &encode_provenance(prov, &secrets));
}
for annotation in module.annotations() {
message(&mut out, 6, &encode_annotation(annotation, &secrets));
}
for (tag, payload) in module.extensions() {
if carries_secret(payload, &secrets) {
return Err(Error::Encode(
"extension payload carries secret material; a projection must not copy it",
));
}
let mut extension = Vec::new();
uint(&mut extension, 1, *tag);
bytes(&mut extension, 2, payload);
message(&mut out, 7, &extension);
}
Ok(out)
}
fn carries_secret(payload: &[u8], secrets: &[String]) -> bool {
secrets.iter().any(|secret| {
let bytes = secret.as_bytes();
!bytes.is_empty()
&& bytes.len() <= payload.len()
&& payload.windows(bytes.len()).any(|window| window == bytes)
})
}
fn encode_annotation(annotation: &crate::ir::Annotation, secrets: &[String]) -> Vec<u8> {
let mut body = Vec::new();
uint(&mut body, 1, annotation.node.index() as u64);
match &annotation.kind {
AnnotationKind::Intent(text) => string(&mut body, 2, &redacted_value(text, secrets)),
AnnotationKind::Constraint { source, expr } => {
let mut constraint = Vec::new();
string(&mut constraint, 1, &redacted_value(source, secrets));
uint(&mut constraint, 2, expr.index() as u64);
message(&mut body, 3, &constraint);
}
AnnotationKind::AiImmutable(flag) => boolean(&mut body, 4, *flag),
}
body
}
fn encode_provenance(prov: &crate::ir::Provenance, secrets: &[String]) -> Vec<u8> {
let mut body = Vec::new();
string(
&mut body,
1,
&redacted_value(prov.producer.as_deref().unwrap_or(""), secrets),
);
uint(&mut body, 2, prov.timestamp_ms);
string(
&mut body,
3,
&redacted_value(prov.model.as_deref().unwrap_or(""), secrets),
);
string(
&mut body,
4,
&redacted_value(prov.tool.as_deref().unwrap_or(""), secrets),
);
if let Some(parent) = &prov.parent {
bytes(&mut body, 5, parent);
}
for entry in &prov.entries {
let mut encoded = Vec::new();
string(&mut encoded, 1, &redacted_key(&entry.key, secrets));
match &entry.value {
ProvValue::Text(text) => string(&mut encoded, 2, &redacted_value(text, secrets)),
ProvValue::Int(number) => sint(&mut encoded, 3, *number),
ProvValue::Flag(flag) => boolean(&mut encoded, 4, *flag),
ProvValue::Hash(hash) => bytes(&mut encoded, 5, hash),
}
message(&mut body, 6, &encoded);
}
body
}