ot_certs/asn1/
codegen.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use 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/// Information about how to refer to a variable in the code.
16#[derive(Debug, Clone)]
17pub enum VariableCodegenInfo {
18    /// Variable can be referred to as a pointer.
19    Pointer {
20        // Expression generating the pointer.
21        ptr_expr: String,
22        // Expression generating the size.
23        size_expr: String,
24    },
25    /// Variable is an integer.
26    Uint32 {
27        // Expression generating the value.
28        value_expr: String,
29    },
30    /// Variable is a boolean,
31    Boolean {
32        // Expression generating the value.
33        value_expr: String,
34    },
35}
36
37/// Information about a variable.
38#[derive(Debug, Clone)]
39pub struct VariableInfo {
40    /// Type of the variable.
41    pub var_type: VariableType,
42    /// How to refer to the variable.
43    pub codegen: VariableCodegenInfo,
44}
45
46// Each CodegenStructure maps to a CodeOutput.
47type 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    // A code snippet.
58    Code(String),
59    // If all the arguments are constants, derive its resulting bytes directly.
60    ConstBytes(Vec<u8>),
61    // Patch the last two bytes with length delta.
62    SizePatchMemo(i16),
63    // Create a new nested block with left curly brace.
64    OpenBlock,
65    // Close a nested block. It should be paired with OpenBlock.
66    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
88/// ASN1 code generator.
89pub struct Codegen<'a> {
90    /// Output buffer.
91    output: CodeOutput,
92    /// Variable types: return information about a variable by name.
93    variable_info: &'a dyn Fn(&str) -> Result<VariableInfo>,
94    /// Minimum size of the output.
95    min_out_size: usize,
96    /// Maximum size of the output.
97    max_out_size: usize,
98}
99
100/// ASN1 code generator output.
101pub struct CodegenOutput {
102    /// Output code.
103    pub code: String,
104    /// Minimum size of the produced cert/TBS.
105    pub min_size: usize,
106    /// Maximum size of the produced cert/TBS.
107    pub max_size: usize,
108}
109
110impl Codegen<'_> {
111    /// Generate code that corresponds to an ASN1 document described by a closure acting on a Builder.
112    /// Returns the generated code and the min/max possible size of the output.
113    ///
114    /// # Arguments
115    ///
116    /// * `buf_name` - Name of the variable holding the pointer to the output buffer.
117    /// * `buf_size_name` - Name of the variable holding the size of the output buffer.
118    ///   This variable is updated by the code to hold the actual size of the data after production.
119    /// * `variables` - Description of the variable types used when producing the output.
120    /// * `build` - Closure generating the ASN1 document.
121    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 neighboring const chunks, we collect them into a single array and write them at once.
140        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                            // The bytes in (nbytes-2..nbytes) will be patched.
157                            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                            // Add comment about the const in-place.
167                            if let Some(comment) = &comment {
168                                output.push_str(&format!("/* {comment} */\n"));
169                            }
170
171                            // But collect the actual bytes to the shared pool.
172                            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                // Output the combined const segment.
183                if nbytes > 0 {
184                    output.push_str(&format!(
185                        "template_push_const(&state, /*nbytes=*/{nbytes});\n"
186                    ));
187                }
188            } else {
189                /* is_code == true */
190                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        // Apply the size delta to the pregenerated const bytes in the reversed order.
203        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    /// Push a chunk to the output stream.
240    fn push_chunk(&mut self, code: CodeChunk, comment: Option<String>) {
241        self.output.push(CodeChunkWithComment { code, comment });
242    }
243
244    /// Start a new nested block in the output stream.
245    /// It should be paired with an `unindent()` call.
246    fn indent(&mut self) {
247        self.push_chunk(CodeChunk::OpenBlock, None);
248    }
249
250    /// Close one level of nested block.
251    /// It should be paired with an `indent()` call.
252    fn unindent(&mut self) {
253        self.push_chunk(CodeChunk::CloseBlock, None);
254    }
255
256    /// Push raw const bytes into the output stream.
257    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    /// Push a constified DER built by the `build` function.
264    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    /// Push raw code with size difference into the output stream.
275    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    /// Get the placeholder for length octet encoded with DER.
282    fn get_size_placeholder(min_size: usize, max_size: usize) -> Result<Vec<u8>> {
283        // DER requires the length octet encoded in minimum bytes.
284        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    /// Return the maximum size of ASN1 tag, ie the tag itself, the length.
305    fn tag_size(max_size: usize) -> usize {
306        // For now, assume that all tags fit on one byte since that's the only
307        // option supported by the asn1 library anyway.
308        let tag_bytes = 1;
309        // The asn1 library only supports tag with up to 0xffff bytes of data
310        // so use this as an upper bound.
311        let len_bytes = if max_size <= 0x7f {
312            // The length fits on a single byte.
313            1
314        } else if max_size <= 0xff {
315            // The length requires one byte to specify the number of bytes
316            // and just one byte to hold the value.
317            2
318        } else if max_size <= 0xffff {
319            // The length requires one byte to specify the number of bytes
320            // and two bytes to hold the value.
321            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    // Return the value that corresponds to a tag and can be passed to asn1 functions.
331    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    /// Push a byte into the ASN1 output, the value can be any C expression.
357    fn push_byte(&mut self, val: u8) -> Result<()> {
358        self.push_const(Some("Byte".into()), &[val]);
359        Ok(())
360    }
361
362    /// Push a tagged boolean into the ASN1 output.
363    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                // Verify that type is correct.
376                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                            // A boolean only requires one byte of data (plus the tag).
389                            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    /// Push a tagged integer into the ASN1 output, the buffer (a big-endian integer) and its size
405    /// are arbitrary C expressions.
406    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                // Get the maximum size and verify that types and conversion are correct.
424                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                // ASN1 integer will add one extra byte when positive integers
447                // have MSB = 1.
448                let (min_size, max_size) = source_type.int_size(1);
449
450                // Value zero will be encoded as one-byte 0x00.
451                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                // Two extra bytes for tag and length.
459                let (min_size, max_size) = (min_size + 2, max_size + 2);
460
461                // For variables, an integer can either be represented by a pointer to a big-endian
462                // byte array, or by a `uint32_t` for a very small integers. Use `template_push_uint32`
463                // or `template_push_integer` depending on the case.
464                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                        // Make sure the type is correct and get the size.
484                        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    /// Push a byte array into the ASN1 output, represeting an integer. If the provided buffer is too small,
500    /// it will be padded with zeroes. Note that this function does not add a tag to the ASN1 output.
501    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                        // There is not tag, we are just pushing the data itself.
538                        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    /// Push a byte array of fixed length into the ASN1 output. Note that this function does not add a tag to
554    /// the ASN1 output.
555    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                        // There is not tag, we are just pushing the data itself.
584                        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    /// Push an optionally tagged string into the ASN1 output.
600    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                // When pushing a variable, it can either a string (use template_push_bytes) or a byte array
618                // that needs to converted (use template_push_hex).
619                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                                // The conversion doubles the size.
661                                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        // See X.690 spec section 8.6 for encoding details.
698        // Note: the encoding of an empty bitstring must be the number of unused bits to 0 and have no content.
699        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        // Serialize oid with tag to const.
754        self.push_der(Some(format!("Oid of {}", oid)), |builder| {
755            builder.push_oid(oid)
756        })
757    }
758
759    // Helper function for outputting ASN1 tags.
760    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        // Allocate placeholders in the output stream.
776        // This placeholder will be replaced by the encoded length later.
777        let len_idx = self.output.len();
778        self.push_const(None, &[]);
779        // This placeholder will be replaced by the location memo code later.
780        let memo_idx = self.output.len();
781        self.push_const(None, &[]);
782
783        let content_idx = self.output.len();
784
785        // We do not yet know how many bytes the content will use: remember the current
786        // value of the estimate and see by how much it increases during generation
787        // to obtain a bound.
788        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        // Generate size patching code.
797        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            // Sanity check for all constants.
805            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            // The memo placeholder is left empty / no-op in this branch.
825        } else {
826            /* var-sized tag */
827            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            // Add the memo statement.
834            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}