Skip to main content

caj2pdf_core/
hnc8.rs

1// SPDX-License-Identifier: MIT
2
3//! Bounded metadata traversal for the three independently measured HN/C8
4//! container profiles. Image payloads and text are never loaded here.
5//! [`convert_document_pdf`] and [`convert_source_pages_pdf`] convert whole
6//! documents; type-0, JPEG and type-3 images are checked and emitted by the
7//! one shared composition pipeline. Composition validates the observed text
8//! frame while retaining only raw image-coordinate words. Empirical geometry remains diagnostic.
9//! [`Hnc8Reader::application_info`] reads the trailing C8 application-info
10//! package's DOI, URL and note count.
11
12mod appinfo;
13mod compose;
14mod inflate;
15mod jpeg;
16mod native;
17mod native_glyphs;
18mod native_page;
19pub use native_glyphs::{MAX_NATIVE_FONT_GLYPHS, NativeFontGlyphAlias};
20mod outline;
21mod placement;
22mod structure;
23mod text;
24mod type3_image;
25
26pub use appinfo::{
27    ApplicationInfo, ApplicationInfoDefect, ApplicationInfoReport, ApplicationInfoStatus,
28    MAX_APPLICATION_INFO_BYTES, MAX_APPLICATION_INFO_FIELD_BYTES,
29};
30pub use compose::{
31    C8FontSource, C8FontSources, ComposeOptions, ComposePage, ComposeReport, ComposeVisitor,
32    ComposedImage, convert_document_pdf, convert_source_pages_pdf, uses_native_text,
33};
34pub use jpeg::{JpegColor, JpegInfo, read_type2_jpeg_info};
35pub(crate) use native::{
36    NativeRecord, NativeRecordVisitor, decode_native_character, decode_native_character_for_mode,
37    decode_native_image_coordinate,
38};
39#[cfg(test)]
40pub(crate) use native_page::labelled_font;
41pub(crate) use native_page::write_c8_native_page;
42pub use native_page::{
43    C8_DEFAULT_DECORATION_ALIAS, C8_MAX_FONTS, C8PageFonts, NativeDecorationGlyph,
44    NativeSymbolGlyph, SymbolFontIdentity, is_mode_zero_symbol,
45};
46pub use outline::{MAX_RECORDED_OUTLINE_DEFECTS, OutlineDefect, OutlineRepair, OutlineReport};
47pub(crate) use placement::{
48    C8GlyphClass, EMPIRICAL_COORDINATE_POINTS_PER_UNIT, EmpiricalPageGeometry,
49    empirical_c8_horizontal_decoration, empirical_c8_segment, empirical_image_transform,
50    empirical_page_from_pixels,
51};
52pub use structure::{ApplicationInfoTail, TextFraming, TextStructure};
53pub use text::RawTextCoordinate;
54pub(crate) use text::TEXT_DECODER_RESERVATION_BYTES;
55
56use crate::jbig1::Type0Span;
57use crate::{
58    Cancellation, Context, Error, ErrorKind, Hnc8Stage, Limits, RangedSource, Result, read_exact_at,
59};
60
61const PAGE_ROW_BYTES: u64 = 20;
62const IMAGE_RECORD_BYTES: u64 = 12;
63const OUTLINE_RECORD_BYTES: u64 = 308;
64
65/// The three measured container layouts; this is not a complete HN standard.
66#[derive(Clone, Copy, Debug, Eq, PartialEq)]
67pub enum Variant {
68    C8,
69    HnA,
70    HnB,
71}
72
73impl Variant {
74    pub const fn as_str(self) -> &'static str {
75        match self {
76            Self::C8 => "C8",
77            Self::HnA => "HN-A",
78            Self::HnB => "HN-B",
79        }
80    }
81}
82
83/// Where a conversion error occurred: the container variant, the one-based
84/// page and image numbers, and the source offset, each when known.
85#[derive(Clone, Copy)]
86struct At {
87    variant: Option<Variant>,
88    page: Option<u32>,
89    image: Option<u32>,
90    offset: Option<u64>,
91}
92
93impl At {
94    const NONE: Self = Self {
95        variant: None,
96        page: None,
97        image: None,
98        offset: None,
99    };
100
101    fn with_offset(self, offset: u64) -> Self {
102        Self {
103            offset: Some(offset),
104            ..self
105        }
106    }
107
108    /// Locate `error` at the conversion `stage`. An unlocated error takes
109    /// this location; an HN/C8 or JBIG2 error keeps its own offset, numbers
110    /// and stage and gains the ones it lacks.
111    fn locate(self, stage: Hnc8Stage, error: Error) -> Error {
112        let context = match error.context {
113            Context::None => Context::Hnc8 {
114                variant: self.variant,
115                page: self.page,
116                image: self.image,
117                segment: None,
118                stage: Some(stage),
119            },
120            Context::Jbig2 { segment } => Context::Hnc8 {
121                variant: self.variant,
122                page: self.page,
123                image: self.image,
124                segment,
125                stage: Some(stage),
126            },
127            Context::Hnc8 {
128                variant,
129                page,
130                image,
131                segment,
132                stage: own,
133            } => Context::Hnc8 {
134                variant: variant.or(self.variant),
135                page: page.or(self.page),
136                image: image.or(self.image),
137                segment,
138                stage: own.or(Some(stage)),
139            },
140            _ => return error,
141        };
142        Error {
143            offset: error.offset.or(self.offset),
144            context,
145            ..error
146        }
147    }
148
149    /// An unlocated error at `stage`.
150    fn error(self, stage: Hnc8Stage, error: Error) -> Error {
151        self.locate(stage, error)
152    }
153
154    /// A mapper that locates an error at `stage`.
155    fn locator(self, stage: Hnc8Stage) -> impl FnOnce(Error) -> Error {
156        move |error| self.locate(stage, error)
157    }
158}
159
160/// A checked absolute interval in the source. The end is exclusive.
161#[derive(Clone, Copy, Debug, Eq, PartialEq)]
162pub struct Span {
163    pub offset: u64,
164    pub length: u64,
165}
166
167impl Span {
168    pub fn checked_end(self) -> Option<u64> {
169        self.offset.checked_add(self.length)
170    }
171}
172
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
174pub struct Header {
175    pub variant: Variant,
176    /// Raw native mode at C8 offset 12 or HN-B offset 148; unknown for HN-A.
177    /// Independently controlled modes 0 and 2 interpret some character codes
178    /// differently. Preserving this word does not admit its rendering profile.
179    pub native_mode: Option<u32>,
180    /// Raw C8 origin at 28/30 or HN-B origin at 164/166; unknown for HN-A.
181    /// The verified native-record profile subtracts these from record coordinates.
182    /// This does not establish image placement or font baseline semantics.
183    pub native_origin: Option<[u16; 2]>,
184    /// Declared page extents in source units; native HN-B uses these too.
185    pub page_size: Option<[u16; 2]>,
186    pub page_count: u32,
187    pub page_index: Span,
188}
189
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub struct PageRecord {
192    pub page_number: u32,
193    pub row_offset: u64,
194    pub text: Span,
195    pub image_count: u32,
196    /// Uninterpreted bytes at page-row offsets +10 through +19.
197    /// For the compact HN-B profile, only +10/+11 exist; the remaining eight
198    /// bytes are padding, not source data. Its admitted third word is zero.
199    pub unknown: [u8; 10],
200}
201
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
203pub struct ImageRecord {
204    pub page_number: u32,
205    pub image_number: u32,
206    pub descriptor_offset: u64,
207    /// Signed descriptor field at +0, checked to be in the observed 0..=3 set.
208    pub record_type: u32,
209    pub payload: Span,
210}
211
212impl ImageRecord {
213    /// Pass exactly the outer type-0 descriptor and absolute payload span to
214    /// the bounded row decoder. Other observed image types remain metadata.
215    pub fn type0_span(self) -> Option<Type0Span> {
216        (self.record_type == 0).then_some(Type0Span {
217            record_type: self.record_type,
218            offset: self.payload.offset,
219            length: self.payload.length,
220        })
221    }
222}
223
224#[derive(Clone, Copy)]
225struct Location {
226    variant: Option<Variant>,
227    offset: u64,
228    page: Option<u32>,
229    image: Option<u32>,
230}
231
232impl Location {
233    fn at(self, offset: u64) -> Self {
234        Self { offset, ..self }
235    }
236
237    fn context(self) -> Context {
238        Context::Hnc8 {
239            variant: self.variant,
240            page: self.page,
241            image: self.image,
242            segment: None,
243            stage: None,
244        }
245    }
246
247    /// Locate an unlocated error here.
248    fn locate(self, error: Error) -> Error {
249        error.or_at(self.offset, self.context())
250    }
251
252    fn malformed(self, reason: &'static str) -> Error {
253        self.locate(Error::invalid(reason))
254    }
255
256    fn unsupported(self, reason: &'static str) -> Error {
257        self.locate(Error::unsupported(self.offset, reason))
258    }
259
260    fn truncated(self, field: &'static str, expected: u64, available: u64) -> Error {
261        self.locate(Error::truncated(self.offset, expected, available).because(field))
262    }
263
264    fn limit(self, resource: &'static str, limit: u64, attempted: u64) -> Error {
265        self.locate(Error::limit(resource, limit, attempted))
266    }
267
268    fn cancelled(self) -> Error {
269        self.locate(Error::cancelled())
270    }
271}
272
273/// Every caller passes an offset and a length below 2^42: fixed header
274/// offsets, a page index after at most `2^31` 308-byte outline records,
275/// page rows within at most `2^31` 20-byte rows, and record fields and
276/// descriptor cursors built from nonnegative i32 values. Their sum therefore
277/// fits u64.
278fn checked_span(
279    source_size: u64,
280    offset: u64,
281    length: u64,
282    loc: Location,
283    field: &'static str,
284) -> Result<Span> {
285    debug_assert!(offset < 1 << 42 && length < 1 << 42);
286    let end = offset + length;
287    if offset > source_size || end > source_size {
288        return Err(loc.truncated(field, length, source_size.saturating_sub(offset)));
289    }
290    Ok(Span { offset, length })
291}
292
293fn signed32(bytes: &[u8]) -> i32 {
294    i32::from_le_bytes(bytes.try_into().expect("fixed field width"))
295}
296
297fn signed16(bytes: &[u8]) -> i16 {
298    i16::from_le_bytes(bytes.try_into().expect("fixed field width"))
299}
300
301/// `reason` names the field and that it is negative.
302fn nonnegative32(value: i32, loc: Location, reason: &'static str) -> Result<u64> {
303    u64::try_from(value).map_err(|_| loc.malformed(reason))
304}
305
306fn read_fixed<S: RangedSource, C: Cancellation>(
307    source: &mut S,
308    limits: &Limits,
309    cancellation: &C,
310    offset: u64,
311    bytes: &mut [u8],
312    loc: Location,
313    field: &'static str,
314) -> Result<()> {
315    checked_span(source.size(), offset, bytes.len() as u64, loc, field)?;
316    let mut done = 0;
317    while done < bytes.len() {
318        let count = (bytes.len() - done).min(limits.io_chunk_bytes);
319        let current = offset + done as u64;
320        read_exact_at(
321            source,
322            current,
323            &mut bytes[done..done + count],
324            limits,
325            cancellation,
326        )
327        .map_err(|error| match error.kind {
328            ErrorKind::Truncated { .. } => loc.at(current).locate(error.because(field)),
329            _ => loc.at(current).locate(error),
330        })?;
331        done += count;
332    }
333    Ok(())
334}
335
336fn overlaps_protected(span: Span, protected_end: u64) -> bool {
337    span.length > 0 && span.offset < protected_end
338}
339
340#[derive(Clone, Copy)]
341struct CurrentPage {
342    page: PageRecord,
343    next_image: u32,
344    next_descriptor: u64,
345}
346
347/// A one-page-at-a-time cursor. A failed read leaves the cursor where it was;
348/// the caller abandons it. The source remains borrowable between records.
349pub struct Hnc8Reader<'a, S: RangedSource, C: Cancellation> {
350    source: &'a mut S,
351    limits: &'a Limits,
352    cancellation: &'a C,
353    header: Header,
354    page_row_bytes: u64,
355    next_page: u32,
356    current: Option<CurrentPage>,
357}
358
359impl<'a, S: RangedSource, C: Cancellation> Hnc8Reader<'a, S, C> {
360    /// Open a cursor at the first page. `limits` are validated here; each
361    /// image payload is bounded by `Limits::max_allocation_bytes`.
362    pub fn open(source: &'a mut S, limits: &'a Limits, cancellation: &'a C) -> Result<Self> {
363        Self::open_starting_at(source, limits, cancellation, 1)
364    }
365
366    /// Open a fresh diagnostic cursor at one page-index row.
367    ///
368    /// This deliberately skips earlier pages so that malformed pages can be
369    /// inspected independently. Full-document conversion must use `open`;
370    /// the CLI's per-page structure report uses this cursor.
371    pub fn probe_at_page(
372        source: &'a mut S,
373        limits: &'a Limits,
374        cancellation: &'a C,
375        start_page: u32,
376    ) -> Result<Self> {
377        Self::open_starting_at(source, limits, cancellation, start_page)
378    }
379
380    fn open_starting_at(
381        source: &'a mut S,
382        limits: &'a Limits,
383        cancellation: &'a C,
384        start_page: u32,
385    ) -> Result<Self> {
386        let base = Location {
387            variant: None,
388            offset: 0,
389            page: None,
390            image: None,
391        };
392        limits.validate().map_err(|source| base.locate(source))?;
393        if cancellation.is_cancelled() {
394            return Err(base.cancelled());
395        }
396        if source.size() > limits.max_input_bytes {
397            return Err(base.limit("source bytes", limits.max_input_bytes, source.size()));
398        }
399        let mut magic = [0; 4];
400        read_fixed(
401            source,
402            limits,
403            cancellation,
404            0,
405            &mut magic,
406            base,
407            "signature",
408        )?;
409        let (variant, count_offset, index_start): (Variant, u64, u64) = match magic {
410            [0xc8, 0, 0, 0] => (Variant::C8, 0x08, 0x50),
411            [b'H', b'N', 0, 0] => {
412                let mut marker = [0; 4];
413                read_fixed(
414                    source,
415                    limits,
416                    cancellation,
417                    4,
418                    &mut marker,
419                    base.at(4),
420                    "HN marker",
421                )?;
422                match marker {
423                    [0x90, 0x01, 0, 0] => (Variant::HnA, 0x90, 0x15c),
424                    [0xc8, 0, 0, 0] => (Variant::HnB, 0x90, 0xd8),
425                    _ => {
426                        return Err(base.at(4).unsupported("HN marker"));
427                    }
428                }
429            }
430            _ => {
431                return Err(base.unsupported("signature"));
432            }
433        };
434        let loc = Location {
435            variant: Some(variant),
436            ..base
437        };
438        let mut count = [0; 4];
439        read_fixed(
440            source,
441            limits,
442            cancellation,
443            count_offset,
444            &mut count,
445            loc.at(count_offset),
446            "page count",
447        )?;
448        let signed_count = signed32(&count);
449        if signed_count <= 0 {
450            return Err(loc
451                .at(count_offset)
452                .malformed("page count: must be positive"));
453        }
454        let page_count = signed_count as u32;
455        if page_count > limits.max_pages {
456            return Err(loc.at(count_offset).limit(
457                "pages",
458                u64::from(limits.max_pages),
459                u64::from(page_count),
460            ));
461        }
462        let native_mode = if variant != Variant::HnA {
463            let offset = count_offset + 4;
464            let mut mode = [0; 4];
465            read_fixed(
466                source,
467                limits,
468                cancellation,
469                offset,
470                &mut mode,
471                loc.at(offset),
472                "native mode",
473            )?;
474            Some(u32::from_le_bytes(mode))
475        } else {
476            None
477        };
478        let native_origin = if variant != Variant::HnA {
479            let offset = count_offset + 20;
480            let mut origin = [0; 4];
481            read_fixed(
482                source,
483                limits,
484                cancellation,
485                offset,
486                &mut origin,
487                loc.at(offset),
488                "native coordinate origin",
489            )?;
490            Some([
491                u16::from_le_bytes([origin[0], origin[1]]),
492                u16::from_le_bytes([origin[2], origin[3]]),
493            ])
494        } else {
495            None
496        };
497        let page_size = {
498            let offset = count_offset + 24;
499            let mut size = [0; 4];
500            read_fixed(
501                source,
502                limits,
503                cancellation,
504                offset,
505                &mut size,
506                loc.at(offset),
507                "page dimensions",
508            )?;
509            Some([
510                u16::from_le_bytes([size[0], size[1]]),
511                u16::from_le_bytes([size[2], size[3]]),
512            ])
513        };
514        let index_start = if variant == Variant::HnA {
515            let mut outline = [0; 4];
516            read_fixed(
517                source,
518                limits,
519                cancellation,
520                0x158,
521                &mut outline,
522                loc.at(0x158),
523                "outline count",
524            )?;
525            let outline_count = nonnegative32(
526                signed32(&outline),
527                loc.at(0x158),
528                "outline count: negative signed value",
529            )?;
530            // The count is below 2^31, so the index starts below 2^42.
531            let outline_bytes = outline_count * OUTLINE_RECORD_BYTES;
532            index_start + outline_bytes
533        } else {
534            index_start
535        };
536        let page_row_bytes = if variant == Variant::HnB {
537            let mut marker = [0; 4];
538            read_fixed(
539                source,
540                limits,
541                cancellation,
542                0x88,
543                &mut marker,
544                loc.at(0x88),
545                "HN-B page-index layout",
546            )?;
547            match u32::from_le_bytes(marker) {
548                0 => 12,
549                0xc8 => PAGE_ROW_BYTES,
550                _ => {
551                    return Err(loc.at(0x88).unsupported("HN-B page-index layout"));
552                }
553            }
554        } else {
555            PAGE_ROW_BYTES
556        };
557        let index_length = u64::from(page_count) * page_row_bytes;
558        let page_index = checked_span(
559            source.size(),
560            index_start,
561            index_length,
562            loc.at(index_start),
563            "page index",
564        )?;
565        if start_page == 0 || start_page > page_count {
566            return Err(loc
567                .at(index_start)
568                .malformed("page number: outside declared page index"));
569        }
570        Ok(Self {
571            source,
572            limits,
573            cancellation,
574            header: Header {
575                variant,
576                native_mode,
577                native_origin,
578                page_size,
579                page_count,
580                page_index,
581            },
582            page_row_bytes,
583            next_page: start_page,
584            current: None,
585        })
586    }
587
588    pub fn header(&self) -> Header {
589        self.header
590    }
591
592    pub fn source_mut(&mut self) -> &mut S {
593        self.source
594    }
595
596    pub fn next_page(&mut self) -> Result<Option<PageRecord>> {
597        let loc = Location {
598            variant: Some(self.header.variant),
599            offset: self.header.page_index.offset,
600            page: Some(self.next_page),
601            image: None,
602        };
603        if let Some(current) = self
604            .current
605            .filter(|page| page.next_image <= page.page.image_count)
606        {
607            return Err(Location {
608                page: Some(current.page.page_number),
609                image: Some(current.next_image),
610                offset: current.next_descriptor,
611                ..loc
612            }
613            .malformed("page has unread image records"));
614        }
615        if self.next_page > self.header.page_count {
616            return Ok(None);
617        }
618        if self.cancellation.is_cancelled() {
619            return Err(loc.cancelled());
620        }
621        let page_number = self.next_page;
622        let row_offset =
623            self.header.page_index.offset + u64::from(page_number - 1) * self.page_row_bytes;
624        let loc = loc.at(row_offset);
625        let mut row = [0; PAGE_ROW_BYTES as usize];
626        read_fixed(
627            self.source,
628            self.limits,
629            self.cancellation,
630            row_offset,
631            &mut row[..self.page_row_bytes as usize],
632            loc,
633            "page row",
634        )?;
635        let text_offset = nonnegative32(
636            signed32(&row[..4]),
637            loc,
638            "text offset: negative signed value",
639        )?;
640        let text_length = nonnegative32(
641            signed32(&row[4..8]),
642            loc.at(row_offset + 4),
643            "text length: negative signed value",
644        )?;
645        let text = checked_span(
646            self.source.size(),
647            text_offset,
648            text_length,
649            loc.at(row_offset),
650            "text span",
651        )?;
652        if self.page_row_bytes == 12 {
653            if text.offset < self.header.page_index.checked_end().expect("checked index") {
654                return Err(loc.malformed("text span: overlaps protected container index"));
655            }
656            let value = u32::from_le_bytes(row[8..12].try_into().expect("four bytes"));
657            if value != 0 {
658                return Err(loc
659                    .at(row_offset + 8)
660                    .unsupported("compact HN-B third word"));
661            }
662        }
663        let signed_images = signed16(&row[8..10]);
664        if signed_images < 0 {
665            return Err(loc
666                .at(row_offset + 8)
667                .malformed("image count: negative signed value"));
668        }
669        let image_count = signed_images as u32;
670        let mut unknown = [0; 10];
671        unknown.copy_from_slice(&row[10..]);
672        let page = PageRecord {
673            page_number,
674            row_offset,
675            text,
676            image_count,
677            unknown,
678        };
679        self.current = Some(CurrentPage {
680            page,
681            next_image: 1,
682            next_descriptor: text.checked_end().expect("checked text span"),
683        });
684        self.next_page += 1;
685        Ok(Some(page))
686    }
687
688    pub fn next_image(&mut self) -> Result<Option<ImageRecord>> {
689        let loc = Location {
690            variant: Some(self.header.variant),
691            offset: self.header.page_index.offset,
692            page: self.current.map(|current| current.page.page_number),
693            image: self.current.map(|current| current.next_image),
694        };
695        let current = self
696            .current
697            .ok_or_else(|| loc.malformed("no current page"))?;
698        if current.next_image > current.page.image_count {
699            return Ok(None);
700        }
701        if self.cancellation.is_cancelled() {
702            return Err(loc.cancelled());
703        }
704        let descriptor_offset = current.next_descriptor;
705        let loc = loc.at(descriptor_offset);
706        let descriptor = checked_span(
707            self.source.size(),
708            descriptor_offset,
709            IMAGE_RECORD_BYTES,
710            loc,
711            "image descriptor",
712        )?;
713        if overlaps_protected(
714            descriptor,
715            self.header
716                .page_index
717                .checked_end()
718                .expect("checked page index"),
719        ) {
720            return Err(loc.malformed("image descriptor: overlaps header or page index"));
721        }
722        let mut bytes = [0; IMAGE_RECORD_BYTES as usize];
723        read_fixed(
724            self.source,
725            self.limits,
726            self.cancellation,
727            descriptor_offset,
728            &mut bytes,
729            loc,
730            "image descriptor",
731        )?;
732        let signed_type = signed32(&bytes[..4]);
733        if signed_type < 0 {
734            return Err(loc.malformed("image type: negative signed value"));
735        }
736        if signed_type > 3 {
737            return Err(loc.unsupported("image type"));
738        }
739        let payload_offset = nonnegative32(
740            signed32(&bytes[4..8]),
741            loc.at(descriptor_offset + 4),
742            "image offset: negative signed value",
743        )?;
744        let payload_length = nonnegative32(
745            signed32(&bytes[8..12]),
746            loc.at(descriptor_offset + 8),
747            "image length: negative signed value",
748        )?;
749        if payload_length == 0 {
750            return Err(loc
751                .at(descriptor_offset + 8)
752                .malformed("image length: zero-length payload"));
753        }
754        let payload = checked_span(
755            self.source.size(),
756            payload_offset,
757            payload_length,
758            loc.at(descriptor_offset + 4),
759            "image payload",
760        )?;
761        if payload_length > self.limits.max_allocation_bytes {
762            return Err(loc.at(descriptor_offset + 8).limit(
763                "image span bytes",
764                self.limits.max_allocation_bytes,
765                payload_length,
766            ));
767        }
768        if payload.offset < descriptor.checked_end().expect("checked descriptor span") {
769            return Err(loc
770                .at(descriptor_offset + 4)
771                .malformed("image offset: payload overlaps descriptor or chain regresses"));
772        }
773        // The payload starts at or after the descriptor's end, and the
774        // descriptor was checked to start after the protected header and
775        // page index.
776        debug_assert!(!overlaps_protected(
777            payload,
778            self.header
779                .page_index
780                .checked_end()
781                .expect("checked page index"),
782        ));
783        let record_type = signed_type as u32;
784        let record = ImageRecord {
785            page_number: current.page.page_number,
786            image_number: current.next_image,
787            descriptor_offset,
788            record_type,
789            payload,
790        };
791        self.current = Some(CurrentPage {
792            next_image: current.next_image + 1,
793            next_descriptor: payload.checked_end().expect("checked image span"),
794            ..current
795        });
796        Ok(Some(record))
797    }
798}