1use serde_json::{Value, json};
44
45use crate::admit::{Requirement, admit};
46use crate::protocol::{Config, IndexLine, delegation_payload};
47
48pub fn admit_json(upload: &[u8], expected: &[u8]) -> String {
51 let expected = match parse_expected(expected) {
52 Ok(e) => e,
53 Err(message) => return error("bad_request", &message),
54 };
55 match admit(
56 upload,
57 expected.as_ref().map(|(n, v)| (n.as_str(), v.as_str())),
58 ) {
59 Ok(a) => json!({ "ok": {
60 "name": a.name,
61 "version": a.version,
62 "checksum": a.checksum,
63 "deps": requirements(&a.deps),
64 "npm": requirements(&a.npm),
65 "archive": base64(&a.archive),
66 }})
67 .to_string(),
68 Err(r) => error(r.code, &r.message),
69 }
70}
71
72pub fn call_json(request: &[u8]) -> String {
74 let v: Value = match serde_json::from_slice(request) {
75 Ok(v) => v,
76 Err(e) => return error("bad_request", &format!("the request isn't JSON: {e}")),
77 };
78 let result = match v.get("op").and_then(Value::as_str) {
79 Some("line_payload") => line_from(&v, false).and_then(|line| {
80 let registry = text(&v, "registry")?;
81 Ok(json!({ "payload": base64(&line.payload(®istry)) }))
82 }),
83 Some("line_render") => line_from(&v, true).map(|line| json!({ "text": line.render() })),
84 Some("signer_info") => signer_info(&v),
85 Some(other) => Err(format!("no operation `{other}`")),
86 None => Err("the request has no `op`".to_string()),
87 };
88 match result {
89 Ok(ok) => json!({ "ok": ok }).to_string(),
90 Err(message) => error("bad_request", &message),
91 }
92}
93
94fn text(v: &Value, k: &str) -> Result<String, String> {
95 v.get(k)
96 .and_then(Value::as_str)
97 .map(str::to_owned)
98 .ok_or_else(|| format!("the request has no string `{k}`"))
99}
100
101fn line_from(v: &Value, signed: bool) -> Result<IndexLine, String> {
104 let line = v.get("line").ok_or("the request has no `line`")?;
105 let reqs = |k: &str| -> Result<Vec<Requirement>, String> {
106 line.get(k)
107 .and_then(Value::as_array)
108 .ok_or_else(|| format!("the line has no list `{k}`"))?
109 .iter()
110 .map(|r| {
111 Some(Requirement {
112 package: r.get("package")?.as_str()?.to_owned(),
113 range: r.get("range")?.as_str()?.to_owned(),
114 })
115 })
116 .collect::<Option<Vec<_>>>()
117 .ok_or_else(|| format!("the line's `{k}` is malformed"))
118 };
119 Ok(IndexLine {
120 name: text(line, "name")?,
121 version: text(line, "version")?,
122 checksum: text(line, "checksum")?,
123 deps: reqs("deps")?,
124 npm: reqs("npm")?,
125 toolchain: line
126 .get("toolchain")
127 .and_then(Value::as_str)
128 .map(str::to_owned),
129 yanked: signed && line.get("yanked").and_then(Value::as_bool).unwrap_or(false),
130 withdrawn: if signed {
132 line.get("withdrawn")
133 .and_then(Value::as_str)
134 .map(str::to_owned)
135 } else {
136 None
137 },
138 sig: if signed {
139 text(line, "sig")?
140 } else {
141 String::new()
142 },
143 })
144}
145
146fn signer_info(v: &Value) -> Result<Value, String> {
147 let config = Config::parse(text(v, "config")?.as_bytes())?;
148 let key = text(v, "key")?;
149 let chain_key = config.keys.last().expect("parse checks").key.clone();
150 let delegation = config.delegations.iter().find(|d| d.key == key);
151 let revoked = config.revoked.iter().any(|r| r.key == key);
152 Ok(json!({
153 "id": config.id,
154 "chain_key": chain_key,
155 "delegated": delegation.is_some(),
156 "delegation_payload": base64(&delegation_payload(&config.id, &key)),
157 "delegation_sig": delegation.and_then(|d| d.sig.clone()),
158 "revoked": revoked,
159 }))
160}
161
162fn error(code: &str, message: &str) -> String {
163 json!({ "error": { "code": code, "message": message } }).to_string()
164}
165
166fn requirements(rs: &[Requirement]) -> Vec<Value> {
167 rs.iter()
168 .map(|r| json!({ "package": r.package, "range": r.range }))
169 .collect()
170}
171
172fn parse_expected(bytes: &[u8]) -> Result<Option<(String, String)>, String> {
173 if bytes.is_empty() {
174 return Ok(None);
175 }
176 let v: Value = serde_json::from_slice(bytes)
177 .map_err(|e| format!("the expected package isn't JSON: {e}"))?;
178 let field = |k: &str| {
179 v.get(k)
180 .and_then(Value::as_str)
181 .map(str::to_owned)
182 .ok_or_else(|| format!("the expected package has no string `{k}`"))
183 };
184 Ok(Some((field("name")?, field("version")?)))
185}
186
187const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
188
189fn base64(bytes: &[u8]) -> String {
190 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
191 for chunk in bytes.chunks(3) {
192 let n = chunk
193 .iter()
194 .enumerate()
195 .fold(0u32, |n, (i, b)| n | (u32::from(*b) << (16 - 8 * i)));
196 for i in 0..4 {
197 if i <= chunk.len() {
198 out.push(B64[(n >> (18 - 6 * i) & 63) as usize] as char);
199 } else {
200 out.push('=');
201 }
202 }
203 }
204 out
205}
206
207#[cfg(target_arch = "wasm32")]
208mod exports {
209 #[unsafe(no_mangle)]
211 pub extern "C" fn alloc(len: usize) -> *mut u8 {
212 let mut buf = Vec::<u8>::with_capacity(len.max(1));
213 let ptr = buf.as_mut_ptr();
214 std::mem::forget(buf);
215 ptr
216 }
217
218 #[unsafe(no_mangle)]
223 pub unsafe extern "C" fn dealloc(ptr: *mut u8, len: usize) {
224 drop(unsafe { Vec::from_raw_parts(ptr, 0, len.max(1)) });
227 }
228
229 #[unsafe(no_mangle)]
234 pub unsafe extern "C" fn admit(
235 ptr: *const u8,
236 len: usize,
237 expected_ptr: *const u8,
238 expected_len: usize,
239 ) -> u64 {
240 let upload = unsafe { std::slice::from_raw_parts(ptr, len) };
242 let expected = if expected_len == 0 {
243 &[][..]
244 } else {
245 unsafe { std::slice::from_raw_parts(expected_ptr, expected_len) }
246 };
247 let out = super::admit_json(upload, expected)
248 .into_bytes()
249 .into_boxed_slice();
250 let (ptr, len) = (out.as_ptr() as u64, out.len() as u64);
251 std::mem::forget(out);
252 ptr << 32 | len
253 }
254}
255
256#[cfg(target_arch = "wasm32")]
257mod call_export {
258 #[unsafe(no_mangle)]
263 pub unsafe extern "C" fn call(ptr: *const u8, len: usize) -> u64 {
264 let request = unsafe { std::slice::from_raw_parts(ptr, len) };
266 let out = super::call_json(request).into_bytes().into_boxed_slice();
267 let (ptr, len) = (out.as_ptr() as u64, out.len() as u64);
268 std::mem::forget(out);
269 ptr << 32 | len
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn base64_matches_the_standard_alphabet() {
279 assert_eq!(base64(b""), "");
280 assert_eq!(base64(b"f"), "Zg==");
281 assert_eq!(base64(b"fo"), "Zm8=");
282 assert_eq!(base64(b"foo"), "Zm9v");
283 assert_eq!(base64(b"foob"), "Zm9vYg==");
284 }
285
286 #[test]
287 fn junk_and_a_bad_expectation_render_as_errors() {
288 let junk = admit_json(b"junk", b"");
289 assert!(junk.contains("\"invalid_package\""), "{junk}");
290 let bad = admit_json(b"junk", b"{");
291 assert!(bad.contains("\"bad_request\""), "{bad}");
292 }
293
294 #[test]
295 fn a_line_payload_and_render_match_the_protocol_types() {
296 let line = IndexLine {
297 name: "acme/money".into(),
298 version: "1.4.2".into(),
299 checksum: "sha256-abc".into(),
300 deps: vec![Requirement {
301 package: "acme/fx".into(),
302 range: "^2".into(),
303 }],
304 npm: vec![],
305 toolchain: None,
306 yanked: true,
307 withdrawn: Some("a takedown".into()),
308 sig: "c2ln".into(),
309 };
310 let fields = json!({
311 "name": "acme/money", "version": "1.4.2", "checksum": "sha256-abc",
312 "deps": [{"package": "acme/fx", "range": "^2"}], "npm": [], "toolchain": null,
313 "yanked": true, "withdrawn": "a takedown", "sig": "c2ln",
314 });
315 let payload = call_json(
316 json!({"op": "line_payload", "line": fields, "registry": "acme"})
317 .to_string()
318 .as_bytes(),
319 );
320 assert_eq!(
321 payload,
322 json!({"ok": {"payload": base64(&line.payload("acme"))}}).to_string()
323 );
324 let rendered = call_json(
325 json!({"op": "line_render", "line": fields})
326 .to_string()
327 .as_bytes(),
328 );
329 assert_eq!(rendered, json!({"ok": {"text": line.render()}}).to_string());
330 }
331
332 #[test]
333 fn signer_info_reports_without_verifying() {
334 let config = r#"{"protocol":2,"id":"bynk","dl":"dl","keys":[{"key":"ed25519:ROOT"}],
335 "delegations":[{"key":"ed25519:ONLINE","sig":"c2ln"}],
336 "revoked":[{"key":"ed25519:OLD","sig":"eA=="}]}"#;
337 let info = |key: &str| {
338 call_json(
339 json!({"op": "signer_info", "config": config, "key": key})
340 .to_string()
341 .as_bytes(),
342 )
343 };
344 let online: Value = serde_json::from_str(&info("ed25519:ONLINE")).unwrap();
345 assert_eq!(online["ok"]["chain_key"], "ed25519:ROOT");
346 assert_eq!(online["ok"]["delegated"], true);
347 assert_eq!(online["ok"]["delegation_sig"], "c2ln");
348 assert_eq!(
349 online["ok"]["delegation_payload"],
350 base64(&delegation_payload("bynk", "ed25519:ONLINE"))
351 );
352 let old: Value = serde_json::from_str(&info("ed25519:OLD")).unwrap();
353 assert_eq!(old["ok"]["revoked"], true);
354 assert_eq!(old["ok"]["delegated"], false);
355 assert!(call_json(b"{\"op\":\"nope\"}").contains("bad_request"));
356 }
357}