1#![allow(unreachable_patterns)]
2
3use std::io::{self, ErrorKind};
4use std::net::{Ipv4Addr, Ipv6Addr};
5
6use ftth_common::channel::{AsyncWorldClient, AsyncWorldServer};
7use futures::TryStreamExt;
8use netlink_packet_core::{DefaultNla, Nla};
9use netlink_packet_route::link::{
10 InfoData, InfoGreTap, InfoGreTap6, InfoGreTun, InfoGreTun6, InfoKind, InfoVlan, LinkMessage,
11};
12use rtnetlink::{LinkMessageBuilder, LinkUnspec};
13
14pub(crate) type Client =
15 AsyncWorldClient<RtnlVirtualInterfaceRequest, RtnlVirtualInterfaceResponse>;
16pub(crate) type Server =
17 AsyncWorldServer<RtnlVirtualInterfaceRequest, RtnlVirtualInterfaceResponse>;
18
19#[derive(Debug, Clone, PartialEq)]
20#[non_exhaustive]
21pub enum RtnlVirtualInterfaceRequest {
22 Create(VirtualInterfaceSpec),
23 Configure(VirtualInterfaceUpdate),
24 Delete(VirtualInterfaceDelete),
25 GetIndexByName(String),
26}
27
28#[derive(Debug, Clone, PartialEq)]
29#[non_exhaustive]
30pub enum RtnlVirtualInterfaceResponse {
31 Success,
32 Failed,
33 NotFound,
34 Index(u32),
35}
36
37#[derive(Debug, Clone, PartialEq)]
38pub struct RtnlVirtualInterfaceClient {
39 client: Client,
40}
41
42impl RtnlVirtualInterfaceClient {
43 pub(crate) fn new(client: Client) -> Self {
44 Self { client }
45 }
46
47 pub fn create(&self, spec: VirtualInterfaceSpec) -> io::Result<()> {
48 let res = self
49 .client
50 .send_request(RtnlVirtualInterfaceRequest::Create(spec))?;
51 handle_basic_response("Create virtual interface", res)
52 }
53
54 pub fn configure(&self, update: VirtualInterfaceUpdate) -> io::Result<()> {
55 let res = self
56 .client
57 .send_request(RtnlVirtualInterfaceRequest::Configure(update))?;
58 handle_basic_response("Configure virtual interface", res)
59 }
60
61 pub fn delete(&self, delete: VirtualInterfaceDelete) -> io::Result<()> {
62 let res = self
63 .client
64 .send_request(RtnlVirtualInterfaceRequest::Delete(delete))?;
65 handle_basic_response("Delete virtual interface", res)
66 }
67
68 pub fn get_index_by_name(&self, name: &str) -> io::Result<u32> {
69 match self
70 .client
71 .send_request(RtnlVirtualInterfaceRequest::GetIndexByName(
72 name.to_string(),
73 ))? {
74 RtnlVirtualInterfaceResponse::Index(index) => Ok(index),
75 RtnlVirtualInterfaceResponse::NotFound => Err(io::Error::new(
76 ErrorKind::NotFound,
77 format!("Virtual interface {name} not found"),
78 )),
79 other => Err(io::Error::other(format!(
80 "Unexpected response while fetching index: {:?}",
81 other
82 ))),
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq)]
88pub struct VirtualInterfaceSpec {
89 pub name: String,
90 pub kind: VirtualInterfaceKind,
91 pub admin_up: bool,
92}
93
94#[derive(Debug, Clone, PartialEq)]
95pub struct VirtualInterfaceUpdate {
96 pub if_id: u32,
97 pub new_name: Option<String>,
98 pub kind: VirtualInterfaceKind,
99 pub admin_up: Option<bool>,
100}
101
102#[derive(Debug, Clone, PartialEq)]
103pub enum VirtualInterfaceDelete {
104 ByIndex(u32),
105 ByName(String),
106}
107
108#[derive(Debug, Clone, PartialEq)]
109pub enum VirtualInterfaceKind {
110 Gre(GreConfig),
111 Gretap(GreConfig),
112 Ip6Gre(Gre6Config),
113 Ip6Gretap(Gre6Config),
114 IpIp(IpIpConfig),
115 Ip6Tnl(Ip6TnlConfig),
116 Vlan(VlanConfig),
117}
118
119#[derive(Debug, Clone, PartialEq)]
120pub struct GreConfig {
121 pub local: Ipv4Addr,
122 pub remote: Ipv4Addr,
123 pub ttl: Option<u8>,
124 pub tos: Option<u8>,
125 pub key: Option<u32>,
126 pub encap_limit: Option<u8>,
127 pub pmtudisc: bool,
128 pub ignore_df: bool,
129 pub link: Option<u32>,
130}
131
132#[derive(Debug, Clone, PartialEq)]
133pub struct Gre6Config {
134 pub local: Ipv6Addr,
135 pub remote: Ipv6Addr,
136 pub hop_limit: Option<u8>,
137 pub traffic_class: Option<u8>,
138 pub key: Option<u32>,
139 pub encap_limit: Option<u8>,
140 pub pmtudisc: bool,
141 pub ignore_df: bool,
142 pub link: Option<u32>,
143}
144
145#[derive(Debug, Clone, PartialEq)]
146pub struct IpIpConfig {
147 pub local: Ipv4Addr,
148 pub remote: Ipv4Addr,
149 pub ttl: Option<u8>,
150 pub tos: Option<u8>,
151 pub encap_limit: Option<u8>,
152 pub pmtudisc: bool,
153 pub link: Option<u32>,
154}
155
156#[derive(Debug, Clone, PartialEq)]
157pub struct Ip6TnlConfig {
158 pub local: Ipv6Addr,
159 pub remote: Ipv6Addr,
160 pub hop_limit: Option<u8>,
161 pub traffic_class: Option<u8>,
162 pub flow_label: Option<u32>,
163 pub encap_limit: Option<u8>,
164 pub pmtudisc: bool,
165 pub link: Option<u32>,
166}
167
168#[derive(Debug, Clone, PartialEq)]
169pub struct VlanConfig {
170 pub base_ifindex: Option<u32>,
171 pub vlan_id: Option<u16>,
172}
173
174const IFLA_GRE_LINK: u16 = 1;
175const IFLA_GRE_IKEY: u16 = 4;
176const IFLA_GRE_OKEY: u16 = 5;
177const IFLA_GRE_LOCAL: u16 = 6;
178const IFLA_GRE_REMOTE: u16 = 7;
179const IFLA_GRE_TTL: u16 = 8;
180const IFLA_GRE_TOS: u16 = 9;
181const IFLA_GRE_PMTUDISC: u16 = 10;
182const IFLA_GRE_ENCAP_LIMIT: u16 = 11;
183const IFLA_GRE_IGNORE_DF: u16 = 19;
184
185const IFLA_IPTUN_LINK: u16 = 1;
186const IFLA_IPTUN_LOCAL: u16 = 2;
187const IFLA_IPTUN_REMOTE: u16 = 3;
188const IFLA_IPTUN_TTL: u16 = 4;
189const IFLA_IPTUN_TOS: u16 = 5;
190const IFLA_IPTUN_ENCAP_LIMIT: u16 = 6;
191const IFLA_IPTUN_FLOWINFO: u16 = 7;
192const IFLA_IPTUN_PMTUDISC: u16 = 10;
193
194const NLA_HEADER_LEN: usize = 4;
195const NLA_ALIGNTO: usize = 4;
196
197fn align_nla(len: usize) -> usize {
198 (len + NLA_ALIGNTO - 1) & !(NLA_ALIGNTO - 1)
199}
200
201fn handle_basic_response(op: &str, response: RtnlVirtualInterfaceResponse) -> io::Result<()> {
202 match response {
203 RtnlVirtualInterfaceResponse::Success => Ok(()),
204 RtnlVirtualInterfaceResponse::NotFound => Err(io::Error::new(
205 ErrorKind::NotFound,
206 format!("{}: target not found", op),
207 )),
208 RtnlVirtualInterfaceResponse::Failed => Err(io::Error::other(format!("{} failed", op))),
209 other => Err(io::Error::other(format!(
210 "{} returned unexpected response: {:?}",
211 op, other
212 ))),
213 }
214}
215
216pub(crate) async fn run_server(mut server: Server, mut handle: rtnetlink::LinkHandle) {
217 while let Some((req, respond)) = server.accept().await {
218 match req {
219 RtnlVirtualInterfaceRequest::Create(spec) => {
220 let message = match build_create_message(&spec) {
221 Ok(msg) => msg,
222 Err(err) => {
223 log::warn!("Failed to build virtual interface {}: {}", spec.name, err);
224 respond(RtnlVirtualInterfaceResponse::Failed);
225 continue;
226 }
227 };
228
229 match handle.add(message).execute().await {
230 Ok(()) => respond(RtnlVirtualInterfaceResponse::Success),
231 Err(rtnetlink::Error::NetlinkError(err_msg)) => {
232 log::warn!(
233 "Netlink error creating virtual interface {}: {}",
234 spec.name,
235 err_msg
236 );
237 respond(netlink_error_to_response(err_msg.to_io()));
238 }
239 Err(err) => {
240 log::warn!("Failed to create virtual interface {}: {}", spec.name, err);
241 respond(RtnlVirtualInterfaceResponse::Failed);
242 }
243 }
244 }
245 RtnlVirtualInterfaceRequest::Configure(update) => {
246 let message = match build_update_message(&update) {
247 Ok(msg) => msg,
248 Err(err) => {
249 log::warn!(
250 "Failed to build virtual interface update for {}: {}",
251 update.if_id,
252 err
253 );
254 respond(RtnlVirtualInterfaceResponse::Failed);
255 continue;
256 }
257 };
258
259 match handle.set(message).execute().await {
260 Ok(()) => respond(RtnlVirtualInterfaceResponse::Success),
261 Err(rtnetlink::Error::NetlinkError(err_msg)) => {
262 respond(netlink_error_to_response(err_msg.to_io()));
263 }
264 Err(err) => {
265 log::warn!(
266 "Failed to configure virtual interface {}: {}",
267 update.if_id,
268 err
269 );
270 respond(RtnlVirtualInterfaceResponse::Failed);
271 }
272 }
273 }
274 RtnlVirtualInterfaceRequest::Delete(delete) => {
275 let result = match resolve_delete_target(&mut handle, &delete).await {
276 Ok(index) => handle.del(index).execute().await.map_err(|err| match err {
277 rtnetlink::Error::NetlinkError(e) => e.to_io(),
278 other => io::Error::other(other.to_string()),
279 }),
280 Err(err) => Err(err),
281 };
282
283 match result {
284 Ok(()) => respond(RtnlVirtualInterfaceResponse::Success),
285 Err(err) if err.kind() == ErrorKind::NotFound => {
286 respond(RtnlVirtualInterfaceResponse::NotFound)
287 }
288 Err(err) => {
289 log::warn!("Failed to delete virtual interface: {}", err);
290 respond(RtnlVirtualInterfaceResponse::Failed);
291 }
292 }
293 }
294 RtnlVirtualInterfaceRequest::GetIndexByName(name) => {
295 match resolve_index_by_name(&mut handle, &name).await {
296 Ok(Some(index)) => respond(RtnlVirtualInterfaceResponse::Index(index)),
297 Ok(None) => respond(RtnlVirtualInterfaceResponse::NotFound),
298 Err(err) => {
299 log::warn!("Failed to resolve virtual interface {}: {}", name, err);
300 respond(RtnlVirtualInterfaceResponse::Failed);
301 }
302 }
303 }
304 }
305 }
306}
307
308fn netlink_error_to_response(err: io::Error) -> RtnlVirtualInterfaceResponse {
309 match err.kind() {
310 ErrorKind::NotFound => RtnlVirtualInterfaceResponse::NotFound,
311 _ => RtnlVirtualInterfaceResponse::Failed,
312 }
313}
314
315fn build_create_message(spec: &VirtualInterfaceSpec) -> io::Result<LinkMessage> {
316 validate_create_kind(&spec.kind)?;
317 let info_kind = virtual_interface_kind_to_info_kind(&spec.kind);
318 let mut builder = LinkMessageBuilder::<LinkUnspec>::new_with_info_kind(info_kind)
319 .name(spec.name.clone())
320 .set_info_data(build_info_data(&spec.kind)?);
321
322 if spec.admin_up {
323 builder = builder.up();
324 }
325
326 if let Some(link) = virtual_interface_link(&spec.kind) {
327 builder = builder.link(link);
328 }
329
330 Ok(builder.build())
331}
332
333fn validate_create_kind(kind: &VirtualInterfaceKind) -> io::Result<()> {
334 match kind {
335 VirtualInterfaceKind::Vlan(cfg) => {
336 if cfg.base_ifindex.is_none() {
337 return Err(io::Error::other(
338 "VLAN creation requires a parent interface (--dev)",
339 ));
340 }
341 if cfg.vlan_id.is_none() {
342 return Err(io::Error::other("VLAN creation requires --vlan-id"));
343 }
344 Ok(())
345 }
346 _ => Ok(()),
347 }
348}
349
350fn build_update_message(update: &VirtualInterfaceUpdate) -> io::Result<LinkMessage> {
351 let info_kind = virtual_interface_kind_to_info_kind(&update.kind);
352 let mut builder = LinkMessageBuilder::<LinkUnspec>::new_with_info_kind(info_kind)
353 .index(update.if_id)
354 .set_info_data(build_info_data(&update.kind)?);
355
356 if let Some(name) = &update.new_name {
357 builder = builder.name(name.clone());
358 }
359
360 if let Some(link) = virtual_interface_link(&update.kind) {
361 builder = builder.link(link);
362 }
363
364 if let Some(up) = update.admin_up {
365 builder = if up { builder.up() } else { builder.down() };
366 }
367
368 Ok(builder.build())
369}
370
371async fn resolve_delete_target(
372 handle: &mut rtnetlink::LinkHandle,
373 delete: &VirtualInterfaceDelete,
374) -> io::Result<u32> {
375 match delete {
376 VirtualInterfaceDelete::ByIndex(index) => Ok(*index),
377 VirtualInterfaceDelete::ByName(name) => resolve_index_by_name(handle, name)
378 .await?
379 .ok_or_else(|| io::Error::new(ErrorKind::NotFound, "Virtual interface not found")),
380 }
381}
382
383async fn resolve_index_by_name(
384 handle: &mut rtnetlink::LinkHandle,
385 name: &str,
386) -> io::Result<Option<u32>> {
387 let response = handle.get().match_name(name.to_string()).execute();
388 futures::pin_mut!(response);
389 while let Ok(Some(msg)) = response.try_next().await {
390 if msg.header.index != 0 {
391 return Ok(Some(msg.header.index));
392 }
393 }
394 Ok(None)
395}
396
397fn virtual_interface_kind_to_info_kind(kind: &VirtualInterfaceKind) -> InfoKind {
398 match kind {
399 VirtualInterfaceKind::Gre(_) => InfoKind::GreTun,
400 VirtualInterfaceKind::Gretap(_) => InfoKind::GreTap,
401 VirtualInterfaceKind::Ip6Gre(_) => InfoKind::GreTun6,
402 VirtualInterfaceKind::Ip6Gretap(_) => InfoKind::GreTap6,
403 VirtualInterfaceKind::IpIp(_) => InfoKind::IpTun,
404 VirtualInterfaceKind::Ip6Tnl(_) => InfoKind::Other("ip6tnl".into()),
405 VirtualInterfaceKind::Vlan(_) => InfoKind::Vlan,
406 }
407}
408
409fn virtual_interface_link(kind: &VirtualInterfaceKind) -> Option<u32> {
410 match kind {
411 VirtualInterfaceKind::Gre(cfg) | VirtualInterfaceKind::Gretap(cfg) => cfg.link,
412 VirtualInterfaceKind::Ip6Gre(cfg) | VirtualInterfaceKind::Ip6Gretap(cfg) => cfg.link,
413 VirtualInterfaceKind::IpIp(cfg) => cfg.link,
414 VirtualInterfaceKind::Ip6Tnl(cfg) => cfg.link,
415 VirtualInterfaceKind::Vlan(cfg) => cfg.base_ifindex,
416 }
417}
418
419fn build_info_data(kind: &VirtualInterfaceKind) -> io::Result<InfoData> {
420 match kind {
421 VirtualInterfaceKind::Gre(cfg) => Ok(InfoData::GreTun(
422 gre_nlas(cfg).into_iter().map(InfoGreTun::Other).collect(),
423 )),
424 VirtualInterfaceKind::Gretap(cfg) => Ok(InfoData::GreTap(
425 gre_nlas(cfg).into_iter().map(InfoGreTap::Other).collect(),
426 )),
427 VirtualInterfaceKind::Ip6Gre(cfg) => Ok(InfoData::GreTun6(
428 gre6_nlas(cfg).into_iter().map(InfoGreTun6::Other).collect(),
429 )),
430 VirtualInterfaceKind::Ip6Gretap(cfg) => Ok(InfoData::GreTap6(
431 gre6_nlas(cfg).into_iter().map(InfoGreTap6::Other).collect(),
432 )),
433 VirtualInterfaceKind::IpIp(cfg) => {
434 let nlas = iptunnel_v4_nlas(cfg);
435 Ok(InfoData::Other(encode_default_nlas(&nlas)))
436 }
437 VirtualInterfaceKind::Ip6Tnl(cfg) => {
438 let nlas = iptunnel_v6_nlas(cfg);
439 Ok(InfoData::Other(encode_default_nlas(&nlas)))
440 }
441 VirtualInterfaceKind::Vlan(cfg) => {
442 let mut infos = Vec::new();
443 if let Some(id) = cfg.vlan_id {
444 infos.push(InfoVlan::Id(id));
445 }
446 Ok(InfoData::Vlan(infos))
447 }
448 }
449}
450
451fn gre_nlas(cfg: &GreConfig) -> Vec<DefaultNla> {
452 let mut nlas = Vec::new();
453 nlas.push(DefaultNla::new(IFLA_GRE_LOCAL, cfg.local.octets().to_vec()));
454 nlas.push(DefaultNla::new(
455 IFLA_GRE_REMOTE,
456 cfg.remote.octets().to_vec(),
457 ));
458
459 if let Some(ttl) = cfg.ttl {
460 nlas.push(DefaultNla::new(IFLA_GRE_TTL, vec![ttl]));
461 }
462
463 if let Some(tos) = cfg.tos {
464 nlas.push(DefaultNla::new(IFLA_GRE_TOS, vec![tos]));
465 }
466
467 if let Some(key) = cfg.key {
468 let bytes = key.to_be_bytes().to_vec();
469 nlas.push(DefaultNla::new(IFLA_GRE_IKEY, bytes.clone()));
470 nlas.push(DefaultNla::new(IFLA_GRE_OKEY, bytes));
471 }
472
473 let limit = cfg.encap_limit.unwrap_or(0xff);
474 nlas.push(DefaultNla::new(IFLA_GRE_ENCAP_LIMIT, vec![limit]));
475
476 nlas.push(DefaultNla::new(
477 IFLA_GRE_PMTUDISC,
478 vec![if cfg.pmtudisc { 1 } else { 0 }],
479 ));
480
481 nlas.push(DefaultNla::new(
482 IFLA_GRE_IGNORE_DF,
483 vec![if cfg.ignore_df { 1 } else { 0 }],
484 ));
485
486 if let Some(link) = cfg.link {
487 nlas.push(DefaultNla::new(IFLA_GRE_LINK, link.to_ne_bytes().to_vec()));
488 }
489
490 nlas
491}
492
493fn gre6_nlas(cfg: &Gre6Config) -> Vec<DefaultNla> {
494 let mut nlas = Vec::new();
495 nlas.push(DefaultNla::new(IFLA_GRE_LOCAL, cfg.local.octets().to_vec()));
496 nlas.push(DefaultNla::new(
497 IFLA_GRE_REMOTE,
498 cfg.remote.octets().to_vec(),
499 ));
500
501 if let Some(hop) = cfg.hop_limit {
502 nlas.push(DefaultNla::new(IFLA_GRE_TTL, vec![hop]));
503 }
504
505 if let Some(tc) = cfg.traffic_class {
506 nlas.push(DefaultNla::new(IFLA_GRE_TOS, vec![tc]));
507 }
508
509 if let Some(key) = cfg.key {
510 let bytes = key.to_be_bytes().to_vec();
511 nlas.push(DefaultNla::new(IFLA_GRE_IKEY, bytes.clone()));
512 nlas.push(DefaultNla::new(IFLA_GRE_OKEY, bytes));
513 }
514
515 let limit = cfg.encap_limit.unwrap_or(0xff);
516 nlas.push(DefaultNla::new(IFLA_GRE_ENCAP_LIMIT, vec![limit]));
517
518 nlas.push(DefaultNla::new(
519 IFLA_GRE_PMTUDISC,
520 vec![if cfg.pmtudisc { 1 } else { 0 }],
521 ));
522
523 nlas.push(DefaultNla::new(
524 IFLA_GRE_IGNORE_DF,
525 vec![if cfg.ignore_df { 1 } else { 0 }],
526 ));
527
528 if let Some(link) = cfg.link {
529 nlas.push(DefaultNla::new(IFLA_GRE_LINK, link.to_ne_bytes().to_vec()));
530 }
531
532 nlas
533}
534
535fn iptunnel_v4_nlas(cfg: &IpIpConfig) -> Vec<DefaultNla> {
536 let mut nlas = Vec::new();
537 nlas.push(DefaultNla::new(
538 IFLA_IPTUN_LOCAL,
539 cfg.local.octets().to_vec(),
540 ));
541 nlas.push(DefaultNla::new(
542 IFLA_IPTUN_REMOTE,
543 cfg.remote.octets().to_vec(),
544 ));
545
546 if let Some(ttl) = cfg.ttl {
547 nlas.push(DefaultNla::new(IFLA_IPTUN_TTL, vec![ttl]));
548 }
549
550 if let Some(tos) = cfg.tos {
551 nlas.push(DefaultNla::new(IFLA_IPTUN_TOS, vec![tos]));
552 }
553
554 let limit = cfg.encap_limit.unwrap_or(0xff);
555 nlas.push(DefaultNla::new(IFLA_IPTUN_ENCAP_LIMIT, vec![limit]));
556
557 nlas.push(DefaultNla::new(
558 IFLA_IPTUN_PMTUDISC,
559 vec![if cfg.pmtudisc { 1 } else { 0 }],
560 ));
561
562 if let Some(link) = cfg.link {
563 nlas.push(DefaultNla::new(
564 IFLA_IPTUN_LINK,
565 link.to_ne_bytes().to_vec(),
566 ));
567 }
568
569 nlas
570}
571
572fn iptunnel_v6_nlas(cfg: &Ip6TnlConfig) -> Vec<DefaultNla> {
573 let mut nlas = Vec::new();
574 nlas.push(DefaultNla::new(
575 IFLA_IPTUN_LOCAL,
576 cfg.local.octets().to_vec(),
577 ));
578 nlas.push(DefaultNla::new(
579 IFLA_IPTUN_REMOTE,
580 cfg.remote.octets().to_vec(),
581 ));
582
583 if let Some(hop) = cfg.hop_limit {
584 nlas.push(DefaultNla::new(IFLA_IPTUN_TTL, vec![hop]));
585 }
586
587 if let Some(tc) = cfg.traffic_class {
588 nlas.push(DefaultNla::new(IFLA_IPTUN_TOS, vec![tc]));
589 }
590
591 if let Some(flow) = cfg.flow_label {
592 nlas.push(DefaultNla::new(
593 IFLA_IPTUN_FLOWINFO,
594 flow.to_be_bytes().to_vec(),
595 ));
596 }
597
598 let limit = cfg.encap_limit.unwrap_or(0xff);
599 nlas.push(DefaultNla::new(IFLA_IPTUN_ENCAP_LIMIT, vec![limit]));
600
601 nlas.push(DefaultNla::new(
602 IFLA_IPTUN_PMTUDISC,
603 vec![if cfg.pmtudisc { 1 } else { 0 }],
604 ));
605
606 if let Some(link) = cfg.link {
607 nlas.push(DefaultNla::new(
608 IFLA_IPTUN_LINK,
609 link.to_ne_bytes().to_vec(),
610 ));
611 }
612
613 nlas
614}
615
616fn encode_default_nlas(nlas: &[DefaultNla]) -> Vec<u8> {
617 let mut buffer = Vec::new();
618 for nla in nlas {
619 let value_len = nla.value_len();
620 let payload_len = value_len + NLA_HEADER_LEN;
621 let aligned_len = align_nla(payload_len);
622 let start = buffer.len();
623 buffer.resize(start + aligned_len, 0);
624 let target = &mut buffer[start..start + aligned_len];
625 let len_bytes = (payload_len as u16).to_ne_bytes();
626 target[0..2].copy_from_slice(&len_bytes);
627 let kind_bytes = (nla.kind() as u16).to_ne_bytes();
628 target[2..4].copy_from_slice(&kind_bytes);
629 nla.emit_value(&mut target[4..payload_len]);
630 }
632 buffer
633}