Skip to main content

idalib/
idb.rs

1use std::ffi::CString;
2use std::marker::PhantomData;
3use std::mem::MaybeUninit;
4#[cfg(not(feature = "plugin"))]
5use std::path::{Path, PathBuf};
6
7use crate::bookmarks::Bookmarks;
8use crate::decompiler::CFunction;
9use crate::ffi::BADADDR;
10use crate::ffi::bytes::*;
11use crate::ffi::comments::{append_cmt, idalib_get_cmt, set_cmt};
12use crate::ffi::conversions::idalib_ea2str;
13use crate::ffi::entry::{get_entry, get_entry_ordinal, get_entry_qty};
14use crate::ffi::func::{
15    add_func, del_func, get_func, get_func_qty, getn_func, idalib_get_func_cmt, idalib_set_func_cmt,
16};
17#[cfg(not(feature = "plugin"))]
18use crate::ffi::hexrays::term_hexrays_plugin;
19use crate::ffi::hexrays::{change_hexrays_config, decompile_function, init_hexrays_plugin};
20#[cfg(not(feature = "plugin"))]
21use crate::ffi::ida::{auto_wait, close_database_with, open_database_quiet};
22use crate::ffi::ida::{is_decompiler_available, make_signatures, set_screen_ea};
23use crate::ffi::insn::decode;
24use crate::ffi::plugin::find_plugin;
25use crate::ffi::processor::get_ph;
26use crate::ffi::search::{idalib_find_defined, idalib_find_imm, idalib_find_text};
27use crate::ffi::segment::{get_segm_by_name, get_segm_qty, getnseg, getseg};
28use crate::ffi::typeinf::{idalib_format_cfunc_decls, idalib_format_decls};
29use crate::ffi::util::{get_imagebase, is_align_insn, next_head, prev_head, str2reg};
30use crate::ffi::xref::{xrefblk_t, xrefblk_t_first_from, xrefblk_t_first_to};
31use crate::func::{Function, FunctionId};
32use crate::insn::{Insn, Register};
33use crate::meta::{Metadata, MetadataMut};
34use crate::name::NameList;
35use crate::plugin::Plugin;
36use crate::processor::Processor;
37use crate::segment::{Segment, SegmentId};
38use crate::strings::StringList;
39use crate::typeinf::FormatDeclsOptions;
40use crate::xref::{XRef, XRefQuery};
41use crate::{Address, AddressFlags, IDAError, IDARuntimeHandle, prepare_library};
42
43pub struct IDB {
44    #[cfg(not(feature = "plugin"))]
45    path: PathBuf,
46    #[cfg(not(feature = "plugin"))]
47    save: bool,
48    decompiler: bool,
49    _guard: IDARuntimeHandle,
50    _marker: PhantomData<*const ()>,
51}
52
53#[derive(Debug, Clone)]
54#[cfg(not(feature = "plugin"))]
55pub struct IDBOpenOptions {
56    idb: Option<PathBuf>,
57    ftype: Option<String>,
58
59    save: bool,
60    auto_analyse: bool,
61}
62
63#[cfg(not(feature = "plugin"))]
64impl Default for IDBOpenOptions {
65    fn default() -> Self {
66        Self {
67            idb: None,
68            ftype: None,
69            save: false,
70            auto_analyse: true,
71        }
72    }
73}
74
75#[cfg(not(feature = "plugin"))]
76impl IDBOpenOptions {
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    pub fn idb(&mut self, path: impl AsRef<Path>) -> &mut Self {
82        self.idb = Some(path.as_ref().to_owned());
83        self
84    }
85
86    pub fn save(&mut self, save: bool) -> &mut Self {
87        self.save = save;
88        self
89    }
90
91    pub fn file_type(&mut self, ftype: impl AsRef<str>) -> &mut Self {
92        self.ftype = Some(ftype.as_ref().to_owned());
93        self
94    }
95
96    pub fn auto_analyse(&mut self, auto_analyse: bool) -> &mut Self {
97        self.auto_analyse = auto_analyse;
98        self
99    }
100
101    pub fn open(&self, path: impl AsRef<Path>) -> Result<IDB, IDAError> {
102        let mut args = Vec::new();
103
104        if let Some(ftype) = self.ftype.as_ref() {
105            args.push(format!("-T{ftype}"));
106        }
107
108        if let Some(idb_path) = self.idb.as_ref() {
109            args.push("-c".to_owned());
110            args.push(format!("-o{}", idb_path.display()));
111        }
112
113        IDB::open_full_with(path, self.auto_analyse, self.save, &args)
114    }
115}
116
117impl IDB {
118    #[cfg(not(feature = "plugin"))]
119    pub fn open(path: impl AsRef<Path>) -> Result<Self, IDAError> {
120        Self::open_with(path, true, false)
121    }
122
123    #[cfg(not(feature = "plugin"))]
124    pub fn open_with(
125        path: impl AsRef<Path>,
126        auto_analyse: bool,
127        save: bool,
128    ) -> Result<Self, IDAError> {
129        Self::open_full_with(path, auto_analyse, save, &[] as &[&str])
130    }
131
132    #[cfg(not(feature = "plugin"))]
133    fn open_full_with(
134        path: impl AsRef<Path>,
135        auto_analyse: bool,
136        save: bool,
137        args: &[impl AsRef<str>],
138    ) -> Result<Self, IDAError> {
139        let _guard = prepare_library();
140        let path = path.as_ref();
141
142        if !path.exists() || !path.is_file() {
143            return Err(IDAError::not_found(path));
144        }
145
146        open_database_quiet(path, auto_analyse, args)?;
147
148        let decompiler = is_decompiler_available() && unsafe { init_hexrays_plugin(0.into()) };
149
150        Ok(Self {
151            path: path.to_owned(),
152            save,
153            decompiler,
154            _guard,
155            _marker: PhantomData,
156        })
157    }
158
159    #[cfg(feature = "plugin")]
160    pub fn current() -> Result<Self, IDAError> {
161        let _guard = prepare_library();
162
163        Ok(Self {
164            decompiler: is_decompiler_available() && unsafe { init_hexrays_plugin(0.into()) },
165            _guard,
166            _marker: PhantomData,
167        })
168    }
169
170    #[cfg(not(feature = "plugin"))]
171    pub fn path(&self) -> &Path {
172        &self.path
173    }
174
175    #[cfg(not(feature = "plugin"))]
176    pub fn save_on_close(&mut self, status: bool) {
177        self.save = status;
178    }
179
180    #[cfg(not(feature = "plugin"))]
181    pub fn auto_wait(&mut self) -> bool {
182        unsafe { auto_wait() }
183    }
184
185    pub fn set_screen_address(&mut self, ea: Address) {
186        set_screen_ea(ea.into());
187    }
188
189    pub fn make_signatures(&mut self, only_pat: bool) -> Result<(), IDAError> {
190        make_signatures(only_pat)
191    }
192
193    pub fn format_decls(&self) -> Result<String, IDAError> {
194        // `INCL_DEPS` pulls in definitions reachable by value, while `DEF_FWD`
195        // covers types only reachable through pointers.
196        self.format_decls_with(FormatDeclsOptions::INCL_DEPS | FormatDeclsOptions::DEF_FWD)
197    }
198
199    pub fn format_decls_with(&self, options: FormatDeclsOptions) -> Result<String, IDAError> {
200        unsafe { idalib_format_decls(options.bits()) }.map_err(IDAError::ffi)
201    }
202
203    pub fn format_cfunc_decls<'a>(&'a self, cfunc: &CFunction<'a>) -> Result<String, IDAError> {
204        // `INCL_DEPS` pulls in definitions reachable by value, while `DEF_FWD`
205        // covers types only reachable through pointers.
206        self.format_cfunc_decls_with(
207            cfunc,
208            FormatDeclsOptions::INCL_DEPS | FormatDeclsOptions::DEF_FWD,
209        )
210    }
211
212    pub fn format_cfunc_decls_with<'a>(
213        &'a self,
214        cfunc: &CFunction<'a>,
215        options: FormatDeclsOptions,
216    ) -> Result<String, IDAError> {
217        unsafe { idalib_format_cfunc_decls(cfunc.as_ptr(), options.bits()) }.map_err(IDAError::ffi)
218    }
219
220    pub fn decompiler_available(&self) -> bool {
221        self.decompiler
222    }
223
224    pub fn modify_decompiler_config(&mut self, directive: impl AsRef<str>) -> Result<(), IDAError> {
225        if !self.decompiler {
226            return Err(IDAError::ffi_with("no decompiler available"));
227        }
228
229        let directive = directive.as_ref();
230        let s = CString::new(directive).map_err(IDAError::ffi)?;
231
232        if unsafe { change_hexrays_config(s.as_ptr()) } {
233            Ok(())
234        } else {
235            Err(IDAError::ffi_with(format!(
236                "failed to apply hexrays config directive `{directive}`"
237            )))
238        }
239    }
240
241    pub fn meta(&self) -> Metadata<'_> {
242        Metadata::new()
243    }
244
245    pub fn meta_mut(&mut self) -> MetadataMut<'_> {
246        MetadataMut::new()
247    }
248
249    pub fn processor(&self) -> Processor<'_> {
250        let ptr = unsafe { get_ph() };
251        Processor::from_ptr(ptr)
252    }
253
254    pub fn entries(&self) -> EntryPointIter<'_> {
255        let limit = unsafe { get_entry_qty() };
256        EntryPointIter {
257            index: 0,
258            limit,
259            _marker: PhantomData,
260        }
261    }
262
263    pub fn function_at(&self, ea: Address) -> Option<Function<'_>> {
264        let ptr = unsafe { get_func(ea.into()) };
265
266        if ptr.is_null() {
267            return None;
268        }
269
270        Some(Function::from_ptr(ptr))
271    }
272
273    pub fn add_function(&mut self, start: Address) -> Result<(), IDAError> {
274        self.add_function_with(start, BADADDR.into())
275    }
276
277    pub fn add_function_with(&mut self, start: Address, end: Address) -> Result<(), IDAError> {
278        if unsafe { add_func(start.into(), end.into()) } {
279            Ok(())
280        } else {
281            Err(IDAError::ffi_with(format!(
282                "failed to add function at {start:#x}"
283            )))
284        }
285    }
286
287    pub fn remove_function(&mut self, start: Address) -> Result<(), IDAError> {
288        if unsafe { del_func(start.into()) } {
289            Ok(())
290        } else {
291            Err(IDAError::ffi_with(format!(
292                "failed to delete function at {start:#x}"
293            )))
294        }
295    }
296
297    pub fn next_head(&self, ea: Address) -> Option<Address> {
298        self.next_head_with(ea, BADADDR.into())
299    }
300
301    pub fn next_head_with(&self, ea: Address, max_ea: Address) -> Option<Address> {
302        let next = unsafe { next_head(ea.into(), max_ea.into()) };
303        if next == BADADDR {
304            None
305        } else {
306            Some(next.into())
307        }
308    }
309
310    pub fn prev_head(&self, ea: Address) -> Option<Address> {
311        self.prev_head_with(ea, 0)
312    }
313
314    pub fn prev_head_with(&self, ea: Address, min_ea: Address) -> Option<Address> {
315        let prev = unsafe { prev_head(ea.into(), min_ea.into()) };
316        if prev == BADADDR {
317            None
318        } else {
319            Some(prev.into())
320        }
321    }
322
323    pub fn insn_at(&self, ea: Address) -> Option<Insn> {
324        let insn = decode(ea.into())?;
325        Some(Insn::from_repr(insn))
326    }
327
328    pub fn decompile<'a>(&'a self, f: &Function<'a>) -> Result<CFunction<'a>, IDAError> {
329        self.decompile_with(f, false)
330    }
331
332    pub fn decompile_with<'a>(
333        &'a self,
334        f: &Function<'a>,
335        all_blocks: bool,
336    ) -> Result<CFunction<'a>, IDAError> {
337        if !self.decompiler {
338            return Err(IDAError::ffi_with("no decompiler available"));
339        }
340
341        Ok(unsafe {
342            decompile_function(f.start_address().into(), all_blocks)
343                .map(|f| CFunction::new(f).expect("null pointer checked"))?
344        })
345    }
346
347    pub fn function_by_id(&self, id: FunctionId) -> Option<Function<'_>> {
348        let ptr = unsafe { getn_func(id) };
349
350        if ptr.is_null() {
351            return None;
352        }
353
354        Some(Function::from_ptr(ptr))
355    }
356
357    pub fn functions<'a>(&'a self) -> impl Iterator<Item = (FunctionId, Function<'a>)> + 'a {
358        (0..self.function_count()).filter_map(|id| self.function_by_id(id).map(|f| (id, f)))
359    }
360
361    pub fn function_count(&self) -> usize {
362        unsafe { get_func_qty() }
363    }
364
365    pub fn segment_at(&self, ea: Address) -> Option<Segment<'_>> {
366        let ptr = unsafe { getseg(ea.into()) };
367
368        if ptr.is_null() {
369            return None;
370        }
371
372        Some(Segment::from_ptr(ptr))
373    }
374
375    pub fn segment_by_id(&self, id: SegmentId) -> Option<Segment<'_>> {
376        let ptr = unsafe { getnseg((id as i32).into()) };
377
378        if ptr.is_null() {
379            return None;
380        }
381
382        Some(Segment::from_ptr(ptr))
383    }
384
385    pub fn segment_by_name(&self, name: impl AsRef<str>) -> Option<Segment<'_>> {
386        let s = CString::new(name.as_ref()).ok()?;
387        let ptr = unsafe { get_segm_by_name(s.as_ptr()) };
388
389        if ptr.is_null() {
390            return None;
391        }
392
393        Some(Segment::from_ptr(ptr))
394    }
395
396    pub fn segments<'a>(&'a self) -> impl Iterator<Item = (SegmentId, Segment<'a>)> + 'a {
397        (0..self.segment_count()).filter_map(|id| self.segment_by_id(id).map(|s| (id, s)))
398    }
399
400    pub fn segment_count(&self) -> usize {
401        unsafe { get_segm_qty().0 as _ }
402    }
403
404    pub fn register_by_name(&self, name: impl AsRef<str>) -> Option<Register> {
405        let s = CString::new(name.as_ref()).ok()?;
406        let id = unsafe { str2reg(s.as_ptr()).0 };
407
408        if id == -1 { None } else { Some(id as _) }
409    }
410
411    pub fn insn_alignment_at(&self, ea: Address) -> Option<usize> {
412        let align = unsafe { is_align_insn(ea.into()).0 };
413        if align == 0 { None } else { Some(align as _) }
414    }
415
416    pub fn first_xref_from(&self, ea: Address, flags: XRefQuery) -> Option<XRef<'_>> {
417        let mut xref = MaybeUninit::<xrefblk_t>::zeroed();
418        let found =
419            unsafe { xrefblk_t_first_from(xref.as_mut_ptr(), ea.into(), flags.bits().into()) };
420
421        if found {
422            Some(XRef::from_repr(unsafe { xref.assume_init() }))
423        } else {
424            None
425        }
426    }
427
428    pub fn first_xref_to(&self, ea: Address, flags: XRefQuery) -> Option<XRef<'_>> {
429        let mut xref = MaybeUninit::<xrefblk_t>::zeroed();
430        let found =
431            unsafe { xrefblk_t_first_to(xref.as_mut_ptr(), ea.into(), flags.bits().into()) };
432
433        if found {
434            Some(XRef::from_repr(unsafe { xref.assume_init() }))
435        } else {
436            None
437        }
438    }
439
440    pub fn get_cmt(&self, ea: Address) -> Option<String> {
441        self.get_cmt_with(ea, false)
442    }
443
444    pub fn get_cmt_with(&self, ea: Address, rptble: bool) -> Option<String> {
445        let s = unsafe { idalib_get_cmt(ea.into(), rptble) };
446
447        if s.is_empty() { None } else { Some(s) }
448    }
449
450    pub fn get_func_cmt(&self, ea: Address) -> Option<String> {
451        self.get_func_cmt_with(ea, false)
452    }
453
454    pub fn get_func_cmt_with(&self, ea: Address, rptble: bool) -> Option<String> {
455        let f = self.function_at(ea)?;
456        let s = unsafe { idalib_get_func_cmt(f.as_ptr() as _, rptble) }.ok()?;
457
458        if s.is_empty() { None } else { Some(s) }
459    }
460
461    pub fn set_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
462        self.set_cmt_with(ea, comm, false)
463    }
464
465    pub fn set_cmt_with(
466        &self,
467        ea: Address,
468        comm: impl AsRef<str>,
469        rptble: bool,
470    ) -> Result<(), IDAError> {
471        let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
472        if unsafe { set_cmt(ea.into(), s.as_ptr(), rptble) } {
473            Ok(())
474        } else {
475            Err(IDAError::ffi_with(format!(
476                "failed to set comment at {ea:#x}"
477            )))
478        }
479    }
480
481    pub fn set_func_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
482        self.set_func_cmt_with(ea, comm, false)
483    }
484
485    pub fn set_func_cmt_with(
486        &self,
487        ea: Address,
488        comm: impl AsRef<str>,
489        rptble: bool,
490    ) -> Result<(), IDAError> {
491        let f = self
492            .function_at(ea)
493            .ok_or_else(|| IDAError::ffi_with(format!("no function found at address {ea:#x}")))?;
494        let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
495        if unsafe { idalib_set_func_cmt(f.as_ptr() as _, s.as_ptr(), rptble) } {
496            Ok(())
497        } else {
498            Err(IDAError::ffi_with(format!(
499                "failed to set function comment at {ea:#x}"
500            )))
501        }
502    }
503
504    pub fn append_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
505        self.append_cmt_with(ea, comm, false)
506    }
507
508    pub fn append_cmt_with(
509        &self,
510        ea: Address,
511        comm: impl AsRef<str>,
512        rptble: bool,
513    ) -> Result<(), IDAError> {
514        let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
515        if unsafe { append_cmt(ea.into(), s.as_ptr(), rptble) } {
516            Ok(())
517        } else {
518            Err(IDAError::ffi_with(format!(
519                "failed to append comment at {ea:#x}"
520            )))
521        }
522    }
523
524    pub fn remove_cmt(&self, ea: Address) -> Result<(), IDAError> {
525        self.remove_cmt_with(ea, false)
526    }
527
528    pub fn remove_cmt_with(&self, ea: Address, rptble: bool) -> Result<(), IDAError> {
529        if unsafe { set_cmt(ea.into(), c"".as_ptr(), rptble) } {
530            Ok(())
531        } else {
532            Err(IDAError::ffi_with(format!(
533                "failed to remove comment at {ea:#x}"
534            )))
535        }
536    }
537
538    pub fn remove_func_cmt(&self, ea: Address) -> Result<(), IDAError> {
539        self.remove_func_cmt_with(ea, false)
540    }
541
542    pub fn remove_func_cmt_with(&self, ea: Address, rptble: bool) -> Result<(), IDAError> {
543        let f = self
544            .function_at(ea)
545            .ok_or_else(|| IDAError::ffi_with(format!("no function found at address {ea:#x}")))?;
546        if unsafe { idalib_set_func_cmt(f.as_ptr(), c"".as_ptr(), rptble) } {
547            Ok(())
548        } else {
549            Err(IDAError::ffi_with(format!(
550                "failed to remove comment at {ea:#x}"
551            )))
552        }
553    }
554
555    pub fn bookmarks(&self) -> Bookmarks<'_> {
556        Bookmarks::new(self)
557    }
558
559    pub fn find_text(&self, start_ea: Address, text: impl AsRef<str>) -> Option<Address> {
560        let s = CString::new(text.as_ref()).ok()?;
561        let addr = unsafe { idalib_find_text(start_ea.into(), s.as_ptr()) };
562        if addr == BADADDR {
563            None
564        } else {
565            Some(addr.into())
566        }
567    }
568
569    pub fn find_text_iter<'a, T>(&'a self, text: T) -> impl Iterator<Item = Address> + 'a
570    where
571        T: AsRef<str> + 'a,
572    {
573        let mut cur = Some(0u64);
574        std::iter::from_fn(move || {
575            let found = self.find_text(cur?, text.as_ref())?;
576            cur = self.find_defined(found);
577            Some(found)
578        })
579    }
580
581    pub fn find_imm(&self, start_ea: Address, imm: u32) -> Option<Address> {
582        let addr = unsafe { idalib_find_imm(start_ea.into(), imm.into()) };
583        if addr == BADADDR {
584            None
585        } else {
586            Some(addr.into())
587        }
588    }
589
590    pub fn find_imm_iter<'a>(&'a self, imm: u32) -> impl Iterator<Item = Address> + 'a {
591        let mut cur = 0u64;
592        std::iter::from_fn(move || {
593            cur = self.find_imm(cur, imm)?;
594            Some(cur)
595        })
596    }
597
598    pub fn find_defined(&self, start_ea: Address) -> Option<Address> {
599        let addr = unsafe { idalib_find_defined(start_ea.into()) };
600        if addr == BADADDR {
601            None
602        } else {
603            Some(addr.into())
604        }
605    }
606
607    pub fn strings(&self) -> StringList<'_> {
608        StringList::new(self)
609    }
610
611    pub fn names(&self) -> crate::name::NameList<'_> {
612        NameList::new(self)
613    }
614
615    pub fn address_to_string(&self, ea: Address) -> Option<String> {
616        let s = unsafe { idalib_ea2str(ea.into()) };
617
618        if s.is_empty() { None } else { Some(s) }
619    }
620
621    pub fn flags_at(&self, ea: Address) -> AddressFlags<'_> {
622        AddressFlags::new(unsafe { get_flags(ea.into()) })
623    }
624
625    pub fn image_base(&self) -> Address {
626        unsafe { get_imagebase() }.into()
627    }
628
629    pub fn get_byte(&self, ea: Address) -> u8 {
630        unsafe { idalib_get_byte(ea.into()) }
631    }
632
633    pub fn get_word(&self, ea: Address) -> u16 {
634        unsafe { idalib_get_word(ea.into()) }
635    }
636
637    pub fn get_dword(&self, ea: Address) -> u32 {
638        unsafe { idalib_get_dword(ea.into()) }
639    }
640
641    pub fn get_qword(&self, ea: Address) -> u64 {
642        unsafe { idalib_get_qword(ea.into()) }
643    }
644
645    pub fn get_bytes(&self, ea: Address, size: usize) -> Vec<u8> {
646        let mut buf = Vec::with_capacity(size);
647
648        let Ok(new_len) = (unsafe { idalib_get_bytes(ea.into(), &mut buf) }) else {
649            return Vec::with_capacity(0);
650        };
651
652        unsafe {
653            buf.set_len(new_len);
654        }
655
656        buf
657    }
658
659    pub fn find_plugin(
660        &self,
661        name: impl AsRef<str>,
662        load_if_needed: bool,
663    ) -> Result<Plugin<'_>, IDAError> {
664        let plugin = CString::new(name.as_ref()).map_err(IDAError::ffi)?;
665        let ptr = unsafe { find_plugin(plugin.as_ptr(), load_if_needed) };
666
667        if ptr.is_null() {
668            Err(IDAError::ffi_with(format!(
669                "failed to load {} plugin",
670                name.as_ref()
671            )))
672        } else {
673            Ok(Plugin::from_ptr(ptr as *const _))
674        }
675    }
676
677    pub fn load_plugin(&self, name: impl AsRef<str>) -> Result<Plugin<'_>, IDAError> {
678        self.find_plugin(name, true)
679    }
680}
681
682#[cfg(not(feature = "plugin"))]
683impl Drop for IDB {
684    fn drop(&mut self) {
685        if self.decompiler {
686            unsafe {
687                term_hexrays_plugin();
688            }
689        }
690        close_database_with(self.save);
691    }
692}
693
694pub struct EntryPointIter<'a> {
695    index: usize,
696    limit: usize,
697    _marker: PhantomData<&'a IDB>,
698}
699
700impl<'a> Iterator for EntryPointIter<'a> {
701    type Item = Address;
702
703    fn next(&mut self) -> Option<Self::Item> {
704        while self.index < self.limit {
705            let index = self.index;
706            self.index += 1;
707
708            let ordinal = unsafe { get_entry_ordinal(index) };
709            let addr = unsafe { get_entry(ordinal) };
710
711            if addr != BADADDR {
712                return Some(addr.into());
713            }
714        }
715
716        None
717    }
718
719    fn size_hint(&self) -> (usize, Option<usize>) {
720        let lim = self.limit - self.index;
721        (0, Some(lim))
722    }
723}