1use anyhow::{Result, bail};
42use serde::Serialize;
43use std::fmt;
44
45pub const MAGIC: &[u8] = b"HTLB\x02";
46const MAGIC_V1: &[u8] = b"HTLB\x01";
47
48pub fn format_version(bytes: &[u8]) -> Option<u8> {
52 if bytes.starts_with(MAGIC) {
53 Some(2)
54 } else if bytes.starts_with(MAGIC_V1) {
55 Some(1)
56 } else {
57 None
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum Kind {
64 Bytecode,
65 Source,
66}
67
68#[derive(Debug, Clone)]
69pub struct Module {
70 pub name: String,
71 pub kind: Kind,
72 pub payload: Vec<u8>,
73}
74
75#[derive(Debug, Clone, Default)]
76pub struct Bundle {
77 pub entry: String,
78 pub fingerprint: Vec<u8>,
81 pub htl_version: String,
82 pub host_modules: Vec<String>,
85 pub modules: Vec<Module>,
86}
87
88impl Bundle {
89 pub fn is_bundle(bytes: &[u8]) -> bool {
90 bytes.starts_with(MAGIC) || bytes.starts_with(MAGIC_V1)
91 }
92
93 pub fn module(&self, name: &str) -> Option<&Module> {
94 self.modules.iter().find(|m| m.name == name)
95 }
96
97 pub fn encode(&self) -> Vec<u8> {
98 let mut buf = Vec::new();
99 buf.extend_from_slice(MAGIC);
100 put_bytes(&mut buf, &self.fingerprint);
101 put_bytes(&mut buf, self.htl_version.as_bytes());
102 put_bytes(&mut buf, self.entry.as_bytes());
103 buf.extend_from_slice(&(self.host_modules.len() as u32).to_le_bytes());
104 for h in &self.host_modules {
105 put_bytes(&mut buf, h.as_bytes());
106 }
107 buf.extend_from_slice(&(self.modules.len() as u32).to_le_bytes());
108 for m in &self.modules {
109 buf.push(match m.kind {
110 Kind::Bytecode => 0,
111 Kind::Source => 1,
112 });
113 put_bytes(&mut buf, m.name.as_bytes());
114 put_bytes(&mut buf, &m.payload);
115 }
116 buf
117 }
118
119 pub fn decode(bytes: &[u8]) -> Result<Self> {
120 if bytes.starts_with(MAGIC_V1) {
121 return Self::decode_v1(&bytes[MAGIC_V1.len()..]);
122 }
123 if !bytes.starts_with(MAGIC) {
124 bail!("not an htl bundle (bad magic)");
125 }
126 let mut cur = &bytes[MAGIC.len()..];
127 let fingerprint = take_bytes(&mut cur)?.to_vec();
128 let htl_version = String::from_utf8(take_bytes(&mut cur)?.to_vec())?;
129 let entry = String::from_utf8(take_bytes(&mut cur)?.to_vec())?;
130 let n = take_u32(&mut cur)? as usize;
131 let mut host_modules = Vec::with_capacity(n);
132 for _ in 0..n {
133 host_modules.push(String::from_utf8(take_bytes(&mut cur)?.to_vec())?);
134 }
135 let count = take_u32(&mut cur)? as usize;
136 let mut modules = Vec::with_capacity(count);
137 for _ in 0..count {
138 let kind = match take_u8(&mut cur)? {
139 0 => Kind::Bytecode,
140 1 => Kind::Source,
141 k => bail!("unknown module kind {k} in bundle"),
142 };
143 let name = String::from_utf8(take_bytes(&mut cur)?.to_vec())?;
144 let payload = take_bytes(&mut cur)?.to_vec();
145 modules.push(Module {
146 name,
147 kind,
148 payload,
149 });
150 }
151 Ok(Self {
152 entry,
153 fingerprint,
154 htl_version,
155 host_modules,
156 modules,
157 })
158 }
159
160 fn decode_v1(mut cur: &[u8]) -> Result<Self> {
161 let entry = String::from_utf8(take_bytes(&mut cur)?.to_vec())?;
162 let count = take_u32(&mut cur)? as usize;
163 let mut modules = Vec::with_capacity(count);
164 for _ in 0..count {
165 let name = String::from_utf8(take_bytes(&mut cur)?.to_vec())?;
166 let payload = take_bytes(&mut cur)?.to_vec();
167 modules.push(Module {
168 name,
169 kind: Kind::Bytecode,
170 payload,
171 });
172 }
173 Ok(Self {
174 entry,
175 modules,
176 ..Default::default()
177 })
178 }
179}
180
181fn put_bytes(buf: &mut Vec<u8>, b: &[u8]) {
182 buf.extend_from_slice(&(b.len() as u32).to_le_bytes());
183 buf.extend_from_slice(b);
184}
185
186fn take_u8(cur: &mut &[u8]) -> Result<u8> {
187 if cur.is_empty() {
188 bail!("truncated bundle");
189 }
190 let b = cur[0];
191 *cur = &cur[1..];
192 Ok(b)
193}
194
195fn take_u32(cur: &mut &[u8]) -> Result<u32> {
196 if cur.len() < 4 {
197 bail!("truncated bundle");
198 }
199 let n = u32::from_le_bytes([cur[0], cur[1], cur[2], cur[3]]);
200 *cur = &cur[4..];
201 Ok(n)
202}
203
204fn take_bytes<'a>(cur: &mut &'a [u8]) -> Result<&'a [u8]> {
205 let n = take_u32(cur)? as usize;
206 if cur.len() < n {
207 bail!("truncated bundle");
208 }
209 let (head, rest) = cur.split_at(n);
210 *cur = rest;
211 Ok(head)
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
219pub struct LuaHeader {
220 pub version: String,
222 pub format: u8,
224 pub instruction_bytes: u8,
225 pub integer_bytes: u8,
226 pub number_bytes: u8,
227 pub endian: &'static str,
229}
230
231impl LuaHeader {
232 pub fn parse(fp: &[u8]) -> Option<Self> {
236 if fp.len() < 23 {
238 return None;
239 }
240 let ver = fp[4];
241 Some(Self {
242 version: format!("{}.{}", ver >> 4, ver & 0xf),
243 format: fp[5],
244 instruction_bytes: fp[12],
245 integer_bytes: fp[13],
246 number_bytes: fp[14],
247 endian: if fp[15] == 0x78 { "little" } else { "big" },
249 })
250 }
251}
252
253impl fmt::Display for LuaHeader {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 write!(
256 f,
257 "Lua {}, format {}, {}/{}/{}, {}-endian",
258 self.version,
259 self.format,
260 self.instruction_bytes,
261 self.integer_bytes,
262 self.number_bytes,
263 self.endian
264 )
265 }
266}
267
268pub fn describe_fingerprint(fp: &[u8]) -> String {
271 match LuaHeader::parse(fp) {
272 Some(h) => h.to_string(),
273 None => format!("{} byte(s)", fp.len()),
274 }
275}