1use anyhow::{Result, bail, ensure};
6use itertools::Itertools;
7use num_bigint_dig::BigUint;
8use std::cmp::max;
9
10use crate::asn1::builder::Builder;
11use crate::asn1::der::Der;
12use crate::asn1::{Oid, Tag};
13use crate::template::{Conversion, Value, Variable, VariableType};
14
15#[derive(Debug, Clone)]
17pub enum VariableCodegenInfo {
18 Pointer {
20 ptr_expr: String,
22 size_expr: String,
24 },
25 Uint32 {
27 value_expr: String,
29 },
30 Boolean {
32 value_expr: String,
34 },
35}
36
37#[derive(Debug, Clone)]
39pub struct VariableInfo {
40 pub var_type: VariableType,
42 pub codegen: VariableCodegenInfo,
44}
45
46type CodeOutput = Vec<CodeChunkWithComment>;
48
49#[derive(Debug, Clone)]
50struct CodeChunkWithComment {
51 code: CodeChunk,
52 comment: Option<String>,
53}
54
55#[derive(Clone, Debug, PartialEq, Eq)]
56enum CodeChunk {
57 Code(String),
59 ConstBytes(Vec<u8>),
61 SizePatchMemo(i16),
63 OpenBlock,
65 CloseBlock,
67}
68
69impl CodeChunk {
70 fn is_constant(&self) -> bool {
71 match self {
72 CodeChunk::Code(_) => false,
73 CodeChunk::ConstBytes(_) => true,
74 CodeChunk::SizePatchMemo(_) => true,
75 CodeChunk::OpenBlock => true,
76 CodeChunk::CloseBlock => true,
77 }
78 }
79 fn len(&self) -> usize {
80 match self {
81 CodeChunk::Code(_) => panic!("Variable has no size"),
82 CodeChunk::ConstBytes(x) => x.len(),
83 _ => 0,
84 }
85 }
86}
87
88pub struct Codegen<'a> {
90 output: CodeOutput,
92 variable_info: &'a dyn Fn(&str) -> Result<VariableInfo>,
94 min_out_size: usize,
96 max_out_size: usize,
98}
99
100pub struct CodegenOutput {
102 pub code: String,
104 pub min_size: usize,
106 pub max_size: usize,
108}
109
110impl Codegen<'_> {
111 pub fn generate(
122 buf_name: &str,
123 buf_size_name: &str,
124 variable_info: &dyn Fn(&str) -> Result<VariableInfo>,
125 build: impl FnOnce(&mut Codegen) -> Result<()>,
126 ) -> Result<CodegenOutput> {
127 let mut builder = Codegen {
128 output: CodeOutput::new(),
129 variable_info,
130 min_out_size: 0,
131 max_out_size: 0,
132 };
133 build(&mut builder)?;
134
135 let mut output = String::new();
136 let mut const_bytes = Vec::<u8>::new();
137 let mut size_patches = Vec::<(usize, i16)>::new();
138
139 for (is_code, chunk) in &builder
141 .output
142 .iter()
143 .chunk_by(|inst| !inst.code.is_constant())
144 {
145 if !is_code {
146 let mut nbytes = 0;
147
148 for CodeChunkWithComment { code, comment } in chunk {
149 nbytes += &code.len();
150 match &code {
151 CodeChunk::OpenBlock => output.push_str("{\n"),
152 CodeChunk::CloseBlock => output.push_str("};\n"),
153 CodeChunk::SizePatchMemo(delta) => {
154 size_patches.push((const_bytes.len(), *delta));
155
156 let patch_offset = nbytes - 2;
158 output.push_str(&format!(
159 "
160 /* Size patch with delta {delta} */
161 template_pos_t memo = template_save_pos(&state, {patch_offset});
162 "
163 ));
164 }
165 CodeChunk::ConstBytes(data) => {
166 if let Some(comment) = &comment {
168 output.push_str(&format!("/* {comment} */\n"));
169 }
170
171 if !data.is_empty() {
173 const_bytes.extend(data);
174 let data = data.iter().map(|ch| format!("0x{:02x}", ch)).join(", ");
175 output.push_str(&format!("/* {data} */\n"));
176 }
177 }
178 _ => unreachable!(),
179 }
180 }
181
182 if nbytes > 0 {
184 output.push_str(&format!(
185 "template_push_const(&state, /*nbytes=*/{nbytes});\n"
186 ));
187 }
188 } else {
189 for CodeChunkWithComment { code, comment } in chunk {
191 if let Some(comment) = comment {
192 output.push_str(&format!("/* {comment} */\n"));
193 }
194 match &code {
195 CodeChunk::Code(inner) => output.push_str(inner),
196 _ => unreachable!(),
197 }
198 }
199 }
200 }
201
202 for (idx, value) in size_patches.iter().rev() {
204 let patch_point = &mut const_bytes[idx - 2..*idx];
205 let old_value = u16::from_be_bytes(patch_point.try_into().unwrap());
206 let new_value = old_value.wrapping_add_signed(*value);
207 patch_point.copy_from_slice(&new_value.to_be_bytes());
208 }
209
210 let const_bytes = const_bytes
211 .iter()
212 .map(|ch| format!("0x{:02x}", ch))
213 .join(", ");
214 let max_size = builder.max_out_size;
215 output = format!(
216 "
217 if (*{buf_size_name} < {max_size}) {{
218 return kErrorCertInvalidSize;
219 }}
220
221 const static uint8_t kTemplateConstBytes[] = {{{const_bytes}}};
222 template_state_t state;
223
224 template_init(&state, {buf_name}, kTemplateConstBytes);
225
226 {output}
227
228 *{buf_size_name} = template_finalize(&state);
229 "
230 );
231
232 Ok(CodegenOutput {
233 code: output,
234 min_size: builder.min_out_size,
235 max_size: builder.max_out_size,
236 })
237 }
238
239 fn push_chunk(&mut self, code: CodeChunk, comment: Option<String>) {
241 self.output.push(CodeChunkWithComment { code, comment });
242 }
243
244 fn indent(&mut self) {
247 self.push_chunk(CodeChunk::OpenBlock, None);
248 }
249
250 fn unindent(&mut self) {
253 self.push_chunk(CodeChunk::CloseBlock, None);
254 }
255
256 fn push_const(&mut self, comment: Option<String>, value: &[u8]) {
258 self.min_out_size += value.len();
259 self.max_out_size += value.len();
260 self.push_chunk(CodeChunk::ConstBytes(value.to_owned()), comment);
261 }
262
263 fn push_der(
265 &mut self,
266 comment: Option<String>,
267 build: impl FnOnce(&mut Der) -> Result<()>,
268 ) -> Result<()> {
269 let content = Der::generate(build)?;
270 self.push_const(comment, &content);
271 Ok(())
272 }
273
274 fn push_function_call(&mut self, min_size: usize, max_size: usize, s: &str) {
276 self.min_out_size += min_size;
277 self.max_out_size += max_size;
278 self.push_chunk(CodeChunk::Code(s.to_owned()), None);
279 }
280
281 fn get_size_placeholder(min_size: usize, max_size: usize) -> Result<Vec<u8>> {
283 let tag_size = Self::tag_size(max_size);
285 ensure!(
286 Self::tag_size(min_size) == tag_size,
287 "Var-sized length octet is not supported,\
288 max={max_size},\
289 min={min_size}"
290 );
291
292 let mut len_enc = Vec::<u8>::new();
293 if max_size <= 0x7f {
294 len_enc.push(0);
295 } else {
296 let nbytes = Self::tag_size(max_size) - 2;
297 len_enc.push(0x80 | (nbytes as u8));
298 len_enc.extend(std::iter::repeat_n(0, nbytes));
299 }
300
301 Ok(len_enc)
302 }
303
304 fn tag_size(max_size: usize) -> usize {
306 let tag_bytes = 1;
309 let len_bytes = if max_size <= 0x7f {
312 1
314 } else if max_size <= 0xff {
315 2
318 } else if max_size <= 0xffff {
319 3
322 } else {
323 panic!("the asn1 library only supports tag with length up to 0xffff");
324 };
325 tag_bytes + len_bytes
326 }
327}
328
329impl Tag {
330 fn codestring(&self) -> String {
332 match self {
333 Tag::Oid => "kAsn1TagNumberOid".into(),
334 Tag::Boolean => "kAsn1TagNumberBoolean".into(),
335 Tag::Integer => "kAsn1TagNumberInteger".into(),
336 Tag::GeneralizedTime => "kAsn1TagNumberGeneralizedTime".into(),
337 Tag::PrintableString => "kAsn1TagNumberPrintableString".into(),
338 Tag::Utf8String => "kAsn1TagNumberUtf8String".into(),
339 Tag::Sequence => "kAsn1TagNumberSequence".into(),
340 Tag::Set => "kAsn1TagNumberSet".into(),
341 Tag::OctetString => "kAsn1TagNumberOctetString".into(),
342 Tag::BitString => "kAsn1TagNumberBitString".into(),
343 &Tag::Context { constructed, value } => format!(
344 "kAsn1TagClassContext{} | {value}",
345 if constructed {
346 " | kAsn1TagFormConstructed"
347 } else {
348 ""
349 }
350 ),
351 }
352 }
353}
354
355impl Builder for Codegen<'_> {
356 fn push_byte(&mut self, val: u8) -> Result<()> {
358 self.push_const(Some("Byte".into()), &[val]);
359 Ok(())
360 }
361
362 fn push_boolean(&mut self, tag: &Tag, val: &Value<bool>) -> Result<()> {
364 match val {
365 Value::Literal(_) => {
366 self.push_der(Some(tag.codestring()), |builder| {
367 builder.push_boolean(tag, val)
368 })?;
369 }
370 Value::Variable(Variable { name, convert }) => {
371 let VariableInfo {
372 codegen,
373 var_type: source_type,
374 } = (self.variable_info)(name)?;
375 match source_type {
377 VariableType::Boolean => ensure!(
378 convert.is_none(),
379 "using an boolean variable for an boolean field cannot specify a conversion"
380 ),
381 _ => bail!(
382 "using a variable of type {source_type:?} for a boolean field is not supported"
383 ),
384 }
385 match codegen {
386 VariableCodegenInfo::Boolean { value_expr } => {
387 self.push_tag(Some(tag.codestring()), tag, |builder| {
388 builder.push_function_call(
390 1,
391 1,
392 &format!("template_push_asn1_bool(&state, {value_expr});\n",),
393 );
394 Ok(())
395 })?;
396 }
397 _ => bail!("internal error: boolean represented by a {source_type:?}"),
398 }
399 }
400 }
401 Ok(())
402 }
403
404 fn push_integer(
407 &mut self,
408 name_hint: Option<String>,
409 tag: &Tag,
410 val: &Value<BigUint>,
411 ) -> Result<()> {
412 match val {
413 Value::Literal(_) => {
414 self.push_der(name_hint.clone(), |builder| {
415 builder.push_integer(name_hint, tag, val)
416 })?;
417 }
418 Value::Variable(Variable { name, convert }) => {
419 let VariableInfo {
420 codegen,
421 var_type: source_type,
422 } = (self.variable_info)(name)?;
423 match source_type {
425 VariableType::Integer { .. } => {
426 ensure!(
427 convert.is_none(),
428 "using an integer variable for an integer field cannot specify a conversion"
429 );
430 }
431 VariableType::ByteArray { .. } => match convert {
432 None => bail!(
433 "using a byte array variable for an integer field must specify a conversion"
434 ),
435 Some(Conversion::BigEndian) => (),
436 _ => bail!(
437 "conversion {:?} from byte array to integer is not supported",
438 convert
439 ),
440 },
441 _ => bail!(
442 "using a variable of type {source_type:?} for an integer field is not supported"
443 ),
444 };
445
446 let (min_size, max_size) = source_type.int_size(1);
449
450 let min_size = max(1, min_size);
452
453 ensure!(
454 max_size < 126,
455 "Integer more than 126 bytes is not supported."
456 );
457
458 let (min_size, max_size) = (min_size + 2, max_size + 2);
460
461 let msb_tweak = source_type.use_msb_tweak();
465 ensure!(
466 !msb_tweak || source_type.has_constant_array_size(),
467 "MSb tweak is only supported with constant array size.",
468 );
469
470 let tag_str = tag.codestring();
471 match codegen {
472 VariableCodegenInfo::Uint32 { value_expr } => self.push_function_call(
473 min_size,
474 max_size,
475 &format!(
476 "template_asn1_uint32(&state, {tag_str}, {msb_tweak}, {value_expr});\n",
477 ),
478 ),
479 VariableCodegenInfo::Pointer {
480 ptr_expr,
481 size_expr,
482 } => {
483 self.push_function_call(
485 min_size,
486 max_size,
487 &format!(
488 "template_asn1_integer(&state, {tag_str}, {msb_tweak}, {ptr_expr}, {size_expr});\n",
489 ),
490 )
491 }
492 _ => bail!("internal error: integer represented by a {source_type:?}"),
493 }
494 }
495 }
496 Ok(())
497 }
498
499 fn push_integer_pad(
502 &mut self,
503 name_hint: Option<String>,
504 val: &Value<BigUint>,
505 size: usize,
506 ) -> Result<()> {
507 match val {
508 Value::Literal(_) => {
509 self.push_der(name_hint.clone(), |builder| {
510 builder.push_integer_pad(name_hint, val, size)
511 })?;
512 }
513 Value::Variable(Variable { name, convert }) => {
514 let VariableInfo {
515 codegen,
516 var_type: source_type,
517 } = (self.variable_info)(name)?;
518 match source_type {
519 VariableType::Integer { .. } => {
520 ensure!(
521 convert.is_none(),
522 "using an integer variable for an integer field cannot specify a conversion"
523 );
524 let VariableCodegenInfo::Pointer {
525 ptr_expr,
526 size_expr,
527 } = codegen
528 else {
529 bail!(
530 "the codegen backend does not support small integers for padded integer fields"
531 );
532 };
533
534 let (min_size, max_size) = source_type.array_size();
535 ensure!(min_size == max_size, "Padding is not supported");
536
537 self.push_function_call(
539 min_size,
540 max_size,
541 &format!("template_push_bytes(&state, {ptr_expr}, {size_expr});\n"),
542 )
543 }
544 _ => bail!(
545 "using a variable of type {source_type:?} for a padded integer field is not supported"
546 ),
547 }
548 }
549 }
550 Ok(())
551 }
552
553 fn push_byte_array(&mut self, name_hint: Option<String>, val: &Value<Vec<u8>>) -> Result<()> {
556 match val {
557 Value::Literal(_) => {
558 self.push_der(name_hint.clone(), |builder| {
559 builder.push_byte_array(name_hint, val)
560 })?;
561 }
562 Value::Variable(Variable { name, convert }) => {
563 let VariableInfo {
564 codegen,
565 var_type: source_type,
566 } = (self.variable_info)(name)?;
567 match source_type {
568 VariableType::ByteArray { .. } => {
569 ensure!(
570 convert.is_none(),
571 "using a byte-array variable for a byte-array field cannot specify a conversion"
572 );
573 let (min_size, max_size) = source_type.array_size();
574 let VariableCodegenInfo::Pointer {
575 ptr_expr,
576 size_expr,
577 } = codegen
578 else {
579 bail!(
580 "internal error: byte-array represented by a VariableCodegenInfo::Uint32"
581 );
582 };
583 self.push_function_call(
585 min_size,
586 max_size,
587 &format!("template_push_bytes(&state, {ptr_expr}, {size_expr});\n"),
588 )
589 }
590 _ => bail!(
591 "using a variable of type {source_type:?} for a byte-array field is not supported",
592 ),
593 }
594 }
595 }
596 Ok(())
597 }
598
599 fn push_string(
601 &mut self,
602 name_hint: Option<String>,
603 str_type: &Tag,
604 val: &Value<String>,
605 ) -> Result<()> {
606 match val {
607 Value::Literal(_) => {
608 self.push_der(name_hint.clone(), |builder| {
609 builder.push_string(name_hint, str_type, val)
610 })?;
611 }
612 Value::Variable(Variable { name, convert }) => {
613 let VariableInfo {
614 codegen,
615 var_type: source_type,
616 } = (self.variable_info)(name)?;
617 match source_type {
620 VariableType::String { .. } => {
621 ensure!(
622 convert.is_none(),
623 "cannot use a convertion from string to string"
624 );
625 let (min_size, max_size) = source_type.array_size();
626 let VariableCodegenInfo::Pointer {
627 ptr_expr,
628 size_expr,
629 } = codegen
630 else {
631 bail!(
632 "internal error: string not represented by a VariableCodegenInfo::Pointer"
633 );
634 };
635 self.push_tag(Some(name.into()), str_type, |builder| {
636 builder.push_function_call(
637 min_size,
638 max_size,
639 &format!("template_push_bytes(&state, (const uint8_t*){ptr_expr}, {size_expr});\n"),
640 );
641 Ok(())
642 })?;
643 }
644 VariableType::ByteArray { .. } => {
645 let (min_size, max_size) = source_type.array_size();
646 match convert {
647 None => bail!(
648 "using a byte array variable for an string field must to specify a conversion"
649 ),
650 Some(Conversion::LowercaseHex) => {
651 let VariableCodegenInfo::Pointer {
652 ptr_expr,
653 size_expr,
654 } = codegen
655 else {
656 bail!(
657 "internal error: string not represented by a VariableCodegenInfo::Pointer"
658 );
659 };
660 self.push_tag(Some(name.into()), str_type, |builder| {
662 builder.push_function_call(
663 2 * min_size,
664 2 * max_size,
665 &format!(
666 "template_push_hex(&state, {ptr_expr}, {size_expr});\n"
667 ),
668 );
669 Ok(())
670 })?;
671 }
672 _ => bail!(
673 "conversion {convert:?} from byte array to string is not supported"
674 ),
675 }
676 }
677 _ => bail!("conversion from to {source_type:?} to string is not supported",),
678 }
679 }
680 }
681 Ok(())
682 }
683
684 fn push_bitstring<'a>(
685 &mut self,
686 name_hint: Option<String>,
687 tag: &Tag,
688 bits: &[Value<bool>],
689 ) -> Result<()> {
690 let bit_consts = bits
691 .iter()
692 .map(|x| match x {
693 Value::Literal(x) => x,
694 _ => &false,
695 })
696 .collect::<Vec<_>>();
697 let nr_bytes = bit_consts.len().div_ceil(8);
700 let mut bytes = vec![0u8; nr_bytes];
701 for (i, bit) in bit_consts.iter().enumerate() {
702 bytes[i / 8] |= (**bit as u8) << (7 - (i % 8));
703 }
704
705 let bitstring_name = format!("{}-bit BitString {}", bits.len(), tag.codestring(),);
706
707 self.push_as_bit_string(
708 Some(bitstring_name),
709 tag,
710 bytes.len() * 8 - bits.len(),
711 |builder| builder.push_byte_array(name_hint.clone(), &Value::Literal(bytes.clone())),
712 )?;
713
714 for (i, bit) in bits.iter().enumerate() {
715 let byte_offset = bytes.len() - i / 8;
716 let bit_offset = 7 - (i % 8);
717
718 if let Value::Variable(Variable { name, convert }) = bit {
719 let VariableInfo {
720 codegen,
721 var_type: source_type,
722 } = (self.variable_info)(name)?;
723 match source_type {
724 VariableType::Boolean => {
725 ensure!(
726 convert.is_none(),
727 "cannot use a convertion from boolean to boolean"
728 );
729 let VariableCodegenInfo::Boolean { value_expr } = codegen else {
730 bail!(
731 "internal error: boolean not represented by a VariableCodegenInfo::Boolean"
732 );
733 };
734 self.push_chunk(
735 CodeChunk::Code(format!(
736 "template_set_bit(&state, {byte_offset}, {bit_offset}, {value_expr});\n"
737 )),
738 Some(name.clone()),
739 );
740 }
741 _ => bail!(
742 "conversion from to {:?} to boolean is not supported",
743 source_type
744 ),
745 }
746 }
747 }
748
749 Ok(())
750 }
751
752 fn push_oid(&mut self, oid: &Oid) -> Result<()> {
753 self.push_der(Some(format!("Oid of {}", oid)), |builder| {
755 builder.push_oid(oid)
756 })
757 }
758
759 fn push_tag(
761 &mut self,
762 name_hint: Option<String>,
763 tag: &Tag,
764 build: impl FnOnce(&mut Self) -> Result<()>,
765 ) -> Result<()> {
766 let tag_name = name_hint.unwrap_or("Unnamed tag".into());
767
768 self.indent();
769
770 self.push_const(
771 Some(format!("Tag {} of {tag_name}", tag.codestring())),
772 &tag.to_der()?,
773 );
774
775 let len_idx = self.output.len();
778 self.push_const(None, &[]);
779 let memo_idx = self.output.len();
781 self.push_const(None, &[]);
782
783 let content_idx = self.output.len();
784
785 let old_min_size = self.min_out_size;
789 let old_max_size = self.max_out_size;
790 self.indent();
791 build(self)?;
792 self.unindent();
793 let min_size: usize = self.min_out_size - old_min_size;
794 let max_size: usize = self.max_out_size - old_max_size;
795
796 let len_enc = Self::get_size_placeholder(min_size, max_size)?;
798 self.min_out_size += len_enc.len();
799 self.max_out_size += len_enc.len();
800
801 if min_size == max_size {
802 self.output[len_idx].comment = Some(format!("Length of fixed-sized {tag_name}"));
803
804 if self.output[content_idx..]
806 .iter()
807 .all(|x| x.code.is_constant())
808 {
809 let total_size: usize = self.output[content_idx..]
810 .iter()
811 .map(|x| x.code.len())
812 .sum();
813 if total_size != min_size || total_size != max_size {
814 println!("{:?}", &self.output[content_idx..]);
815 panic!(
816 "Invalid const size total={total_size}, \
817 min={min_size}, M={max_size}"
818 );
819 }
820 }
821
822 let len_enc = Der::encode_size(max_size);
823 self.output[len_idx].code = CodeChunk::ConstBytes(len_enc);
824 } else {
826 self.output[len_idx].comment = Some(format!(
828 "Length of {tag_name} between {min_size} ~ {max_size}"
829 ));
830
831 self.output[len_idx].code = CodeChunk::ConstBytes(len_enc);
832
833 self.output[memo_idx] = CodeChunkWithComment {
835 code: CodeChunk::SizePatchMemo(-2),
836 comment: Some(format!("Start of {tag_name}")),
837 };
838 self.push_chunk(
839 CodeChunk::Code("template_patch_size_be(&state, memo);\n".to_string()),
840 Some(format!("End of {tag_name}")),
841 );
842 }
843 self.unindent();
844 Ok(())
845 }
846}