Skip to content

Commit 626954a

Browse files
authored
add workers-sdk producer section (#984)
1 parent dd24c89 commit 626954a

2 files changed

Lines changed: 356 additions & 0 deletions

File tree

worker-build/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod build_lock;
1818
mod emoji;
1919
mod lockfile;
2020
mod main_legacy;
21+
mod producers;
2122
mod versions;
2223

2324
use build::{Build, BuildOptions};
@@ -109,6 +110,8 @@ pub fn main() -> Result<()> {
109110
wasm_coredump(&staging_dir)?;
110111
}
111112

113+
producers::inject_workers_rs_sdk_metadata(&staging_dir, VERSION)?;
114+
112115
if module_target {
113116
let shim = if builder.panic_unwind {
114117
SHIM_UNWIND_FILE

worker-build/src/producers.rs

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
use std::{fs, path::Path};
2+
3+
use anyhow::{Context, Result};
4+
5+
#[derive(Clone, Copy)]
6+
struct ProducersEntry<'a> {
7+
field: &'a str,
8+
name: &'a str,
9+
version: &'a str,
10+
}
11+
12+
struct ProducersSection {
13+
start: usize,
14+
end: usize,
15+
content_start: usize,
16+
content_end: usize,
17+
}
18+
19+
pub(crate) fn inject_workers_rs_sdk_metadata(out_dir: &Path, version: &'static str) -> Result<()> {
20+
let sdk_entry = ProducersEntry {
21+
field: "sdk",
22+
name: "workers-rs",
23+
version,
24+
};
25+
26+
for entry in fs::read_dir(out_dir)
27+
.with_context(|| format!("Failed to read directory {}", out_dir.display()))?
28+
{
29+
let path = entry?.path();
30+
if path.extension().and_then(|s| s.to_str()) != Some("wasm") {
31+
continue;
32+
}
33+
34+
let bytes =
35+
fs::read(&path).with_context(|| format!("Failed to read {}", path.display()))?;
36+
let updated = merge_producers_section(&bytes, &[sdk_entry]).with_context(|| {
37+
format!("Failed to update producers metadata in {}", path.display())
38+
})?;
39+
fs::write(&path, updated).with_context(|| format!("Failed to write {}", path.display()))?;
40+
}
41+
42+
Ok(())
43+
}
44+
45+
fn merge_producers_section<'a>(
46+
bytes: &'a [u8],
47+
new_entries: &[ProducersEntry<'a>],
48+
) -> Result<Vec<u8>> {
49+
let existing = find_producers_section(bytes)?;
50+
let mut entries = Vec::new();
51+
52+
if let Some(section) = &existing {
53+
parse_producers(
54+
bytes,
55+
section.content_start,
56+
section.content_end,
57+
&mut entries,
58+
)?;
59+
}
60+
61+
for new_entry in new_entries {
62+
if !entries
63+
.iter()
64+
.any(|entry| entry.field == new_entry.field && entry.name == new_entry.name)
65+
{
66+
entries.push(*new_entry);
67+
}
68+
}
69+
70+
let producers_section = encode_producers_section(&entries)?;
71+
let mut output = Vec::with_capacity(bytes.len() + producers_section.len() + 8);
72+
73+
if let Some(section) = existing {
74+
output.extend_from_slice(&bytes[..section.start]);
75+
output.extend_from_slice(&producers_section);
76+
output.extend_from_slice(&bytes[section.end..]);
77+
} else {
78+
output.extend_from_slice(bytes);
79+
output.extend_from_slice(&producers_section);
80+
}
81+
82+
Ok(output)
83+
}
84+
85+
fn find_producers_section(bytes: &[u8]) -> Result<Option<ProducersSection>> {
86+
ensure_wasm_header(bytes)?;
87+
88+
let mut pos = 8;
89+
while pos < bytes.len() {
90+
let section_start = pos;
91+
let section_id = *bytes
92+
.get(pos)
93+
.ok_or_else(|| anyhow::anyhow!("invalid wasm section"))?;
94+
pos += 1;
95+
96+
let section_len = read_u32_leb128(bytes, &mut pos)? as usize;
97+
let section_end = pos
98+
.checked_add(section_len)
99+
.filter(|end| *end <= bytes.len())
100+
.ok_or_else(|| anyhow::anyhow!("invalid wasm section length"))?;
101+
102+
if section_id == 0 {
103+
let name_len = read_u32_leb128(bytes, &mut pos)? as usize;
104+
let name_end = pos
105+
.checked_add(name_len)
106+
.filter(|end| *end <= section_end)
107+
.ok_or_else(|| anyhow::anyhow!("invalid wasm custom section name"))?;
108+
let name = std::str::from_utf8(&bytes[pos..name_end])?;
109+
pos = name_end;
110+
111+
if name == "producers" {
112+
return Ok(Some(ProducersSection {
113+
start: section_start,
114+
end: section_end,
115+
content_start: pos,
116+
content_end: section_end,
117+
}));
118+
}
119+
}
120+
121+
pos = section_end;
122+
}
123+
124+
Ok(None)
125+
}
126+
127+
fn parse_producers<'a>(
128+
bytes: &'a [u8],
129+
content_start: usize,
130+
content_end: usize,
131+
entries: &mut Vec<ProducersEntry<'a>>,
132+
) -> Result<()> {
133+
let mut pos = content_start;
134+
let field_count = read_u32_leb128(bytes, &mut pos)?;
135+
136+
for _ in 0..field_count {
137+
let field = read_wasm_name(bytes, &mut pos, content_end)?;
138+
let value_count = read_u32_leb128(bytes, &mut pos)?;
139+
140+
for _ in 0..value_count {
141+
let name = read_wasm_name(bytes, &mut pos, content_end)?;
142+
let version = read_wasm_name(bytes, &mut pos, content_end)?;
143+
entries.push(ProducersEntry {
144+
field,
145+
name,
146+
version,
147+
});
148+
}
149+
}
150+
151+
if pos != content_end {
152+
anyhow::bail!("invalid producers section");
153+
}
154+
155+
Ok(())
156+
}
157+
158+
fn encode_producers_section(entries: &[ProducersEntry<'_>]) -> Result<Vec<u8>> {
159+
let mut content = Vec::new();
160+
let mut fields = Vec::new();
161+
162+
for entry in entries {
163+
if !fields.contains(&entry.field) {
164+
fields.push(entry.field);
165+
}
166+
}
167+
168+
append_u32_leb128(&mut content, fields.len().try_into()?);
169+
for field in fields {
170+
append_wasm_name(&mut content, field)?;
171+
172+
let field_entries = entries
173+
.iter()
174+
.filter(|entry| entry.field == field)
175+
.collect::<Vec<_>>();
176+
append_u32_leb128(&mut content, field_entries.len().try_into()?);
177+
178+
for entry in field_entries {
179+
append_wasm_name(&mut content, entry.name)?;
180+
append_wasm_name(&mut content, entry.version)?;
181+
}
182+
}
183+
184+
let mut payload = Vec::new();
185+
append_wasm_name(&mut payload, "producers")?;
186+
payload.extend_from_slice(&content);
187+
188+
let mut section = vec![0];
189+
append_u32_leb128(&mut section, payload.len().try_into()?);
190+
section.extend_from_slice(&payload);
191+
192+
Ok(section)
193+
}
194+
195+
fn ensure_wasm_header(bytes: &[u8]) -> Result<()> {
196+
if bytes.len() < 8 || &bytes[..4] != b"\0asm" || bytes[4..8] != [1, 0, 0, 0] {
197+
anyhow::bail!("invalid wasm header");
198+
}
199+
Ok(())
200+
}
201+
202+
fn read_wasm_name<'a>(bytes: &'a [u8], pos: &mut usize, limit: usize) -> Result<&'a str> {
203+
let len = read_u32_leb128(bytes, pos)? as usize;
204+
let end = pos
205+
.checked_add(len)
206+
.filter(|end| *end <= limit)
207+
.ok_or_else(|| anyhow::anyhow!("invalid wasm name"))?;
208+
let name = std::str::from_utf8(&bytes[*pos..end])?;
209+
*pos = end;
210+
Ok(name)
211+
}
212+
213+
fn append_wasm_name(bytes: &mut Vec<u8>, name: &str) -> Result<()> {
214+
append_u32_leb128(bytes, name.len().try_into()?);
215+
bytes.extend_from_slice(name.as_bytes());
216+
Ok(())
217+
}
218+
219+
fn read_u32_leb128(bytes: &[u8], pos: &mut usize) -> Result<u32> {
220+
let mut result = 0u32;
221+
let mut shift = 0;
222+
223+
loop {
224+
let byte = *bytes
225+
.get(*pos)
226+
.ok_or_else(|| anyhow::anyhow!("invalid LEB128 value"))?;
227+
*pos += 1;
228+
229+
result |= u32::from(byte & 0x7f) << shift;
230+
if byte & 0x80 == 0 {
231+
return Ok(result);
232+
}
233+
234+
shift += 7;
235+
if shift >= 32 {
236+
anyhow::bail!("LEB128 value overflow");
237+
}
238+
}
239+
}
240+
241+
fn append_u32_leb128(bytes: &mut Vec<u8>, mut value: u32) {
242+
loop {
243+
let mut byte = (value & 0x7f) as u8;
244+
value >>= 7;
245+
if value != 0 {
246+
byte |= 0x80;
247+
}
248+
bytes.push(byte);
249+
if value == 0 {
250+
break;
251+
}
252+
}
253+
}
254+
255+
#[cfg(test)]
256+
mod test {
257+
use super::{find_producers_section, merge_producers_section, parse_producers, ProducersEntry};
258+
259+
const EMPTY_WASM: &[u8] = b"\0asm\x01\0\0\0";
260+
261+
#[test]
262+
fn test_merge_producers_adds_sdk() {
263+
let wasm = merge_producers_section(
264+
EMPTY_WASM,
265+
&[ProducersEntry {
266+
field: "sdk",
267+
name: "workers-rs",
268+
version: "1.2.3",
269+
}],
270+
)
271+
.unwrap();
272+
273+
let entries = producers_entries(&wasm);
274+
assert!(entries.iter().any(|entry| {
275+
entry.field == "sdk" && entry.name == "workers-rs" && entry.version == "1.2.3"
276+
}));
277+
}
278+
279+
#[test]
280+
fn test_merge_producers_preserves_existing_field_name_pair() {
281+
let wasm = merge_producers_section(
282+
EMPTY_WASM,
283+
&[ProducersEntry {
284+
field: "sdk",
285+
name: "workers-rs",
286+
version: "1.2.3",
287+
}],
288+
)
289+
.unwrap();
290+
let wasm = merge_producers_section(
291+
&wasm,
292+
&[ProducersEntry {
293+
field: "sdk",
294+
name: "workers-rs",
295+
version: "4.5.6",
296+
}],
297+
)
298+
.unwrap();
299+
300+
let entries = producers_entries(&wasm);
301+
let workers_rs_entries = entries
302+
.iter()
303+
.filter(|entry| entry.field == "sdk" && entry.name == "workers-rs")
304+
.collect::<Vec<_>>();
305+
assert_eq!(workers_rs_entries.len(), 1);
306+
assert_eq!(workers_rs_entries[0].version, "1.2.3");
307+
}
308+
309+
#[test]
310+
fn test_merge_producers_preserves_existing_entries() {
311+
let wasm = merge_producers_section(
312+
EMPTY_WASM,
313+
&[ProducersEntry {
314+
field: "processed-by",
315+
name: "wasm-bindgen",
316+
version: "0.2.0",
317+
}],
318+
)
319+
.unwrap();
320+
let wasm = merge_producers_section(
321+
&wasm,
322+
&[ProducersEntry {
323+
field: "sdk",
324+
name: "workers-rs",
325+
version: "1.2.3",
326+
}],
327+
)
328+
.unwrap();
329+
330+
let entries = producers_entries(&wasm);
331+
assert!(entries.iter().any(|entry| {
332+
entry.field == "processed-by"
333+
&& entry.name == "wasm-bindgen"
334+
&& entry.version == "0.2.0"
335+
}));
336+
assert!(entries.iter().any(|entry| {
337+
entry.field == "sdk" && entry.name == "workers-rs" && entry.version == "1.2.3"
338+
}));
339+
}
340+
341+
fn producers_entries(wasm: &[u8]) -> Vec<ProducersEntry<'_>> {
342+
let section = find_producers_section(wasm).unwrap().unwrap();
343+
let mut entries = Vec::new();
344+
parse_producers(
345+
wasm,
346+
section.content_start,
347+
section.content_end,
348+
&mut entries,
349+
)
350+
.unwrap();
351+
entries
352+
}
353+
}

0 commit comments

Comments
 (0)