Crypt-SecretBuffer

 view release on metacpan or  search on metacpan

SecretBuffer.xs  view on Meta::CPAN

   }
#elif defined(HAVE_MINCORE)
   #define CAN_SCAN_MEMORY 1
   #include <sys/mman.h>
   static bool is_page_accessible(uintptr_t addr) {
      unsigned char vec;
      return mincore((void*)addr, 1, &vec) == 0;
   }
#else
   #define CAN_SCAN_MEMORY 0
#endif

/* The rest only works if we have is_page_accessible */
#if CAN_SCAN_MEMORY
IV scan_mapped_memory_in_range(uintptr_t p, uintptr_t lim, const char *needle, size_t needle_len) {
   size_t pagesize= get_page_size();
   size_t count= 0;
   void *at;
   uintptr_t run_start = p, run_lim;
   p = (p & ~(pagesize - 1)); /* round to nearest page, from here out */
   while (p < lim) {
      /* Skip pages that aren't mapped */
      while (p < lim && !is_page_accessible(p)) {
         p += pagesize;
         run_start= p;
      }
      /* This page is mapped.  Find the end of this mapped range, if it comes before lim */
      while (p < lim && is_page_accessible(p)) {
         p += pagesize;
      }
      run_lim= p < lim? p : lim;
      /* Scan memory from run_start to run_lim */
      while (run_start < run_lim && (at= memmem((void*)run_start, run_lim - run_start, needle, needle_len))) {
         ++count;
         run_start= ((intptr_t)at) + needle_len;
      }
   }
   return count;
}
#else
IV scan_mapped_memory_in_range(uintptr_t p, uintptr_t lim, const char *needle, size_t needle_len) {
   return -1;
}
#endif

/**********************************************************************************************\
* Crypt::SecretBuffer API
\**********************************************************************************************/
MODULE = Crypt::SecretBuffer           PACKAGE = Crypt::SecretBuffer
PROTOTYPES: DISABLE

void
new(...)
   ALIAS:
      Crypt::SecretBuffer::Exports::secret = 1
      Crypt::SecretBuffer::Exports::secret_buffer = 2
   INIT:
      SV *buf_ref= NULL;
      secret_buffer *buf= secret_buffer_new(0, &buf_ref);
      int i, next_arg= ix == 0? 1 : 0;
   PPCODE:
      if (items - next_arg == 1) {
         secret_buffer_splice_sv(buf, 0, 0, ST(next_arg));
      }
      else {
         if ((items - next_arg) & 1)
            croak("Odd number of arguments; expected (key => value) pairs");
         for (i= next_arg; i < items-1; i += 2) {
            SV *key= ST(i), *val= ST(i+1);
            {
               dSP;
               ENTER;
               SAVETMPS;
               PUSHMARK(SP);
               EXTEND(SP, 2);
               PUSHs(buf_ref);
               PUSHs(val);
               PUTBACK;
               call_method(SvPV_nolen(key), G_DISCARD);
               FREETMPS;
               LEAVE;
            }
         }
      }
      PUSHs(buf_ref);

void
append(buf, ...)
   auto_secret_buffer buf
   ALIAS:
      assign = 1
   INIT:
      int i;
      size_t from_ofs= (ix? 0 : buf->len), len_sum= 0;
      U8 *write_pos;
      secret_buffer_byte_range stack_ranges[16], *ranges= stack_ranges;
      if ((items-1) > 16) {
         Newx(ranges, (items-1), secret_buffer_byte_range);
         SAVEFREEPV(ranges);
      }
   PPCODE:
      for (i= 0; i < items-1; i++) {
         ranges[i].ptr= secret_buffer_SvPVbyte(ST(i+1), &ranges[i].len);
         len_sum += ranges[i].len;
      }
      secret_buffer_set_len(buf, from_ofs + len_sum);
      write_pos= buf->data + from_ofs;
      for (i= 0; i < items-1; i++) {
         memcpy(write_pos, ranges[i].ptr, ranges[i].len);
         write_pos += ranges[i].len;
      }
      XSRETURN(1); /* return self for chaining */

void
length(buf, val=NULL)
   auto_secret_buffer buf
   SV *val
   ALIAS:
      len = 1
   PPCODE:
      if (val) { /* writing */
         IV ival= SvIV(val);
         if (ival < 0) ival= 0;
         secret_buffer_set_len(buf, ival);
         /* return self, for chaining */
      }
      else /* reading */
         ST(0)= sv_2mortal(newSViv(buf->len));
      XSRETURN(1);

void
capacity(buf, val=NULL, flags= 0)
   auto_secret_buffer buf
   SV *val
   secret_buffer_alloc_flags flags
   PPCODE:
      if (val) { /* wiritng */
         IV ival= SvIV(val);
         if (ival < 0) ival= 0;
         if (flags & SECRET_BUFFER_AT_LEAST)
            secret_buffer_alloc_at_least(buf, ival);
         else
            secret_buffer_realloc(buf, ival);
         /* return self, for chaining */
      }
      else /* reading */
         ST(0)= sv_2mortal(newSViv(buf->capacity));
      XSRETURN(1);

void
clear(buf)
   auto_secret_buffer buf
   PPCODE:
      secret_buffer_realloc(buf, 0);
      XSRETURN(1); /* self, for chaining */

IV
index(buf, pattern, ofs_sv= &PL_sv_undef)
   secret_buffer *buf
   SV *pattern
   SV *ofs_sv
   ALIAS:
      rindex = 1
   INIT:
      secret_buffer_parse parse;
      size_t pos= 0, lim;
      int flags= 0;
      if (ix == 0) { // index (forward)
         pos= normalize_offset(SvOK(ofs_sv)? SvIV(ofs_sv) : 0, buf->len);
         lim= buf->len;
      } else { // rindex (reverse)
         IV max= normalize_offset(SvOK(ofs_sv)? SvIV(ofs_sv) : -1, buf->len);
         flags= SECRET_BUFFER_MATCH_REVERSE;
         // The ofs specifies the *start* of the match, not the maximum byte pos
         // that could be part of the match.  If pattern is a charset, add one to get 'lim',
         // and if pattern is a string, add string byte length to get 'lim'.
         if (SvRX(pattern))
            lim= max + 1;
         else {
            STRLEN len; // needs to be byte count, so can't SvCUR without converting to bytes first
            (void) SvPVbyte(pattern, len);
            lim= max + len;
         }
         // re-clamp lim to end of buffer
         if (lim > buf->len) lim= buf->len;
      }
      if (!secret_buffer_parse_init(&parse, buf, pos, lim, 0))
         croak("%s", parse.error);
   CODE:
      if (secret_buffer_match(&parse, pattern, flags))
         RETVAL= parse.pos - (U8*) buf->data;
      else {
         if (parse.error)
            croak("%s", parse.error);
         RETVAL= -1;
      }
   OUTPUT:
      RETVAL

void
scan(buf, pattern, flags= 0, ofs= 0, len_sv= &PL_sv_undef)
   secret_buffer *buf
   SV *pattern
   IV flags
   IV ofs
   SV *len_sv
   INIT:
      secret_buffer_parse parse;
      // lim was captured as an SV so that undef can be used to indicate
      // end of the buffer.
      IV len= !SvOK(len_sv)? buf->len : SvIV(len_sv);
      ofs= normalize_offset(ofs, buf->len);
      if (!secret_buffer_parse_init(&parse, buf,
         ofs, ofs + normalize_offset(len, buf->len - ofs),
         (flags & SECRET_BUFFER_ENCODING_MASK)
      ))
         croak("%s", parse.error);
   PPCODE:
      if (secret_buffer_match(&parse, pattern, flags)) {
         PUSHs(sv_2mortal(newSViv(parse.pos - (U8*) buf->data)));
         PUSHs(sv_2mortal(newSViv(parse.lim - parse.pos)));
      }
      else if (parse.error)
         croak("%s", parse.error);

void
splice(buf, ofs, len, replacement)
   secret_buffer *buf
   IV ofs
   IV len
   SV *replacement
   PPCODE:
      /* normalize negative offset, and clamp to valid range */
      ofs= normalize_offset(ofs, buf->len);
      /* normalize negative count, and clamp to valid range */
      len= normalize_offset(len, buf->len - ofs);
      secret_buffer_splice_sv(buf, ofs, len, replacement);
      XSRETURN(1); /* return $self */

void
substr(buf, ofs, count_sv=NULL, replacement=NULL)
   secret_buffer *buf
   IV ofs
   SV *count_sv
   SV *replacement
   INIT:
      unsigned char *sub_start;
      secret_buffer *sub_buf= NULL;
      SV *sub_ref= NULL;
      IV count= count_sv? SvIV(count_sv) : buf->len;
   PPCODE:
      /* normalize negative offset, and clamp to valid range */
      ofs= normalize_offset(ofs, buf->len);
      /* normalize negative count, and clamp to valid range */
      count= normalize_offset(count, buf->len - ofs);
      sub_start= (unsigned char*) buf->data + ofs;
      /* If called in non-void context, construct new secret from this range */
      if (GIMME_V != G_VOID) {
         SV **el;
         sub_buf= secret_buffer_new(count, &sub_ref);
         if (count) {
            Copy(sub_start, sub_buf->data, count, unsigned char);
            sub_buf->len= count;
         }
         /* inherit the stringify_mask */
         el= hv_fetchs((HV*) SvRV(ST(0)), "stringify_mask", 0);
         if (el && *el)
            /* we know the hv isn't tied because we just created it, so no need to check success */
            hv_stores((HV*) SvRV(sub_ref), "stringify_mask", newSVsv(*el));
      }
      /* modifying string? */
      if (replacement)
         secret_buffer_splice_sv(buf, ofs, count, replacement);
      /* If void context, return nothing.  Else return the substr */
      if (!sub_ref)
         XSRETURN(0);
      else {
         ST(0)= sub_ref; /* already mortal */
         XSRETURN(1);
      }

void
append_asn1_der_length(buf, val)
   secret_buffer *buf
   UV val
   PPCODE:
      secret_buffer_append_uv_asn1_der_length(buf, val);

void
append_base128le(buf, val)
   secret_buffer *buf
   UV val
   PPCODE:
      secret_buffer_append_uv_base128le(buf, val);

void
append_base128be(buf, val)
   secret_buffer *buf
   UV val
   PPCODE:
      secret_buffer_append_uv_base128be(buf, val);

void
append_lenprefixed(buf, ...)
   secret_buffer *buf
   INIT:
      size_t bytes_needed= 0;
      IV i;
   PPCODE:
      /* Add up all the lengths and over-estimate 9 bytes for each length specifier */
      for (i= 1; i < items; i++) {
         STRLEN len;
         secret_buffer_SvPVbyte(ST(i), &len);
         bytes_needed += 9 + len;
      }
      /* ensure space with one reallocation */
      secret_buffer_alloc_at_least(buf, buf->len + bytes_needed);
      /* append each length and span to the buffer */
      for (i= 1; i < items; i++) {
         STRLEN len;
         size_t buf_pos;
         const char *data= secret_buffer_SvPVbyte(ST(i), &len);
         secret_buffer_append_uv_base128be(buf, len);
         buf_pos= buf->len;
         secret_buffer_set_len(buf, buf_pos + len);
         memcpy(buf->data + buf_pos, data, len);
      }

IV
memcmp(lhs, rhs, reverse=false)
   SV *lhs
   SV *rhs
   bool reverse
   ALIAS:
      Crypt::SecretBuffer::Span::memcmp = 1
      Crypt::SecretBuffer::Exports::memcmp = 2
   INIT:
      STRLEN lhs_len, rhs_len;
      const char *lhs_buf= secret_buffer_SvPVbyte(lhs, &lhs_len);
      const char *rhs_buf= secret_buffer_SvPVbyte(rhs, &rhs_len);
      PERL_UNUSED_VAR(ix);
   CODE:
      /* constant-time loop */
      {
         volatile int ret= 0;
         int i, common_len= lhs_len < rhs_len? lhs_len : rhs_len;
         for (i = 0; i < common_len; i++)
            if (lhs_buf[i] != rhs_buf[i] && !ret)
               ret= lhs_buf[i] < rhs_buf[i]? -1 : 1;
         if (ret == 0 && lhs_len != rhs_len)
            ret= lhs_len < rhs_len? -1 : 1;
         RETVAL= ret;
      }
      if (reverse)
         RETVAL= -RETVAL;
   OUTPUT:
      RETVAL

UV
append_random(buf, count, flags=0)
   auto_secret_buffer buf
   UV count
   secret_buffer_io_flags flags
   CODE:
      RETVAL= secret_buffer_append_random(buf, count, flags);
   OUTPUT:
      RETVAL

void
append_sysread(buf, handle, count)
   auto_secret_buffer buf
   PerlIO *handle
   IV count
   INIT:
      IV got;
   PPCODE:
      got= secret_buffer_append_sysread(buf, handle, count);
      if (got < 0)
         XSRETURN_UNDEF;
      else
         PUSHs(sv_2mortal(newSViv(got)));

void
append_read(buf, handle, count)
   auto_secret_buffer buf
   PerlIO *handle
   IV count
   INIT:
      int got;
   PPCODE:
      got= secret_buffer_append_read(buf, handle, count);
      if (got < 0)
         XSRETURN_UNDEF;
      else
         PUSHs(sv_2mortal(newSViv(got)));

void
_append_console_line(buf, handle)
   auto_secret_buffer buf
   PerlIO *handle
   INIT:
      int got;
   PPCODE:
      got= secret_buffer_append_console_line(buf, handle);
      ST(0)= got == SECRET_BUFFER_GOTLINE? &PL_sv_yes
         : got == SECRET_BUFFER_EOF? &PL_sv_no
         : &PL_sv_undef;
      XSRETURN(1);

void
syswrite(buf, io, count=buf->len, ofs=0)
   secret_buffer *buf
   PerlIO *io
   IV ofs
   IV count
   INIT:
      IV wrote;
   PPCODE:
      wrote= secret_buffer_syswrite(buf, io, ofs, count);
      ST(0)= (wrote < 0)? &PL_sv_undef : sv_2mortal(newSViv(wrote));
      XSRETURN(1);

void
write_async(buf, io, count=buf->len, ofs=0)
   secret_buffer *buf
   PerlIO *io
   IV ofs
   IV count
   INIT:
      IV wrote;
      SV *ref_out= NULL;
   PPCODE:
      wrote= secret_buffer_write_async(buf, io, ofs, count, GIMME_V == G_VOID? NULL : &ref_out);
      /* wrote == 0 means that it supplied a result promise object, which is already mortal.
       * but avoid creating one when called in void context. */
      ST(0)= wrote? sv_2mortal(newSViv(wrote)) : ref_out? ref_out : &PL_sv_undef;
      XSRETURN(1);

void
stringify(buf, ...)
   auto_secret_buffer buf
   INIT:
      SV **field= hv_fetch((HV*)SvRV(ST(0)), "stringify_mask", 14, 0);
   PPCODE:
      if (!field || !*field) {
         ST(0)= sv_2mortal(newSVpvn("[REDACTED]", 10));
      } else if (SvOK(*field)) {
         ST(0)= *field;
      } else {
         ST(0)= secret_buffer_get_stringify_sv(buf);
      }
      XSRETURN(1);

void
unmask_to(buf, coderef)
   secret_buffer *buf
   SV *coderef
   INIT:
      int count= 0;
   PPCODE:
      PUSHMARK(SP);
      EXTEND(SP, 1);
      PUSHs(secret_buffer_get_stringify_sv(buf));
      PUTBACK;
      count= call_sv(coderef, GIMME_V);
      SPAGAIN;
      XSRETURN(count);

bool
_can_count_copies_in_process_memory()
   CODE:
      RETVAL= false;
   OUTPUT:
      RETVAL
   
IV
_count_matches_in_mem(buf, addr0, addr1)
   secret_buffer *buf
   UV addr0
   UV addr1
   CODE:
      if (!buf->len)
         croak("Empty buffer");
      RETVAL= scan_mapped_memory_in_range(addr0, addr1, buf->data, buf->len);
      if (RETVAL < 0)
         croak("Unimplemented");
   OUTPUT:
      RETVAL

MODULE = Crypt::SecretBuffer           PACKAGE = Crypt::SecretBuffer::Exports

void
unmask_secrets_to(coderef, ...)
   SV *coderef
   INIT:
      int count= 0, i;
      secret_buffer *buf= NULL;
   PPCODE:
      PUSHMARK(SP);
      EXTEND(SP, items);
      for (i= 1; i < items; i++) {
         if (SvOK(ST(i)) && SvROK(ST(i)) && (buf= secret_buffer_from_magic(ST(i), 0)))
            PUSHs(secret_buffer_get_stringify_sv(buf));
         else
            PUSHs(ST(i));
      }
      PUTBACK;
      count= call_sv(coderef, GIMME_V);
      SPAGAIN;
      XSRETURN(count);

bool
_wait_fh_readable(handle, timeout_sv)
   PerlIO *handle
   SV *timeout_sv
   CODE:
      RETVAL= sb_wait_fh_readable(aTHX_ handle, timeout_sv);
   OUTPUT:
      RETVAL

void
_debug_charset(cset)
   secret_buffer_charset *cset
   INIT:
      HV *hv;
   PPCODE:
      PUSHs(sv_2mortal((SV*)newRV_noinc((SV*)(hv= newHV()))));
      hv_stores(hv, "bitmap", newSVpvn((char*)cset->bitmap, sizeof(cset->bitmap)));
      hv_stores(hv, "unicode_above_7F", newSViv(cset->unicode_above_7F));

MODULE = Crypt::SecretBuffer           PACKAGE = Crypt::SecretBuffer::AsyncResult

void
wait(result, timeout=-1)
   secret_buffer_async_result *result
   NV timeout
   INIT:
      IV os_err, bytes_written;
   PPCODE:
      if (secret_buffer_async_result_recv(result, (IV)(timeout*1000), &bytes_written, &os_err)) {
         EXTEND(sp, 2);
         ST(0)= sv_2mortal(newSViv(bytes_written));
         ST(1)= sv_2mortal(newSViv(os_err));
         XSRETURN(2);
      } else {
         XSRETURN(0);
      }

MODULE = Crypt::SecretBuffer           PACKAGE = Crypt::SecretBuffer::ConsoleState

void
new(pkg, ...)
   const char *pkg;
   ALIAS:
      maybe_new = 1
   INIT:
      sb_console_state cstate, *magic_cstate;
      PerlIO *handle= NULL;
      SV *auto_restore= NULL;
      SV *set_echo= NULL;
      SV *set_line_input= NULL;
      SV *objref;
      bool already_set= true;
      if (items == 2) {
         IO *io = sv_2io(ST(1));
         handle= io? IoIFP(io) : NULL;
      } else if (items > 2) {
         int i= 1;
         if ((items - 1) & 1)
            croak("expected even-length key/value attribute list");
         for (; i < items; i+= 2) {
            STRLEN len;
            const char *name= SvPV(ST(i), len);
            SV *val= ST(i+1);
            if (len == 4 && memcmp(name, "echo", 4) == 0) {
               if (SvOK(val)) set_echo= val;
            }
            else if (len == 6 && memcmp(name, "handle", 6) == 0) {
               IO *io = sv_2io(val);
               handle= io? IoIFP(io) : NULL;
            }
            else if (len == 10 && memcmp(name, "line_input", 10) == 0) {
               if (SvOK(val)) set_line_input= val;
            }
            else if (len == 12 && memcmp(name, "auto_restore", 12) == 0) {
               if (SvOK(val)) auto_restore= val;
            }
            else {
               croak("Unknown option '%s'", name);
            }
         }
      }
      if (!handle)
         croak("'handle' is required");
   PPCODE:
      ST(0)= &PL_sv_undef;
      /* if it fails to initialize, return undef for 'maybe_new', else die */
      if (!sb_console_state_init(aTHX_ &cstate, handle)) {
         if (ix == 0)
            croak("Can't read console/tty state");
         XSRETURN(1);
      }
      if (auto_restore)
         cstate.auto_restore= SvTRUE(auto_restore);
      if (set_echo) {
         bool enable= SvTRUE(set_echo);
         if (sb_console_state_get_echo(&cstate) != enable) {
            already_set= false;
            if (!sb_console_state_set_echo(&cstate, enable))
               croak("set echo = %d failed", (int)enable);
         }
      }
      if (set_line_input) {
         bool enable= SvTRUE(set_line_input);
         if (sb_console_state_get_line_input(&cstate) != enable) {
            already_set= false;
            if (!sb_console_state_set_line_input(&cstate, enable))
               croak("set echo = %d failed", (int)enable);
         }
      }
      /* if user called 'maybe_new' and echo state aready matches requested
         state, return undef. */
      if (ix == 1 && (set_echo || set_line_input) && already_set)
         XSRETURN(1);
      /* new blessed ConsoleState object */
      ST(0)= objref= sv_2mortal(newRV_noinc(&PL_sv_yes));
      newSVrv(objref, pkg);
      /* move cstate into MAGIC on object */
      magic_cstate= secret_buffer_console_state_from_magic(objref, SECRET_BUFFER_MAGIC_AUTOCREATE);
      *magic_cstate= cstate;
      /* duplicate file handle in case user closes it */
      sb_console_state_dup_fd(magic_cstate);
      XSRETURN(1);

bool
echo(cstate, enable=NULL)
   sb_console_state *cstate
   SV *enable
   CODE:
      if (enable != NULL)
         sb_console_state_set_echo(cstate, SvTRUE(enable));
      RETVAL= sb_console_state_get_echo(cstate);
   OUTPUT:
      RETVAL

bool
line_input(cstate, enable=NULL)
   sb_console_state *cstate
   SV *enable
   CODE:
      if (enable != NULL)
         sb_console_state_set_line_input(cstate, SvTRUE(enable));
      RETVAL= sb_console_state_get_line_input(cstate);
   OUTPUT:
      RETVAL

SecretBuffer.xs  view on Meta::CPAN

         cstate->auto_restore= SvTRUE(enable);
      RETVAL= cstate->auto_restore;
   OUTPUT:
      RETVAL

bool
restore(cstate)
   sb_console_state *cstate
   CODE:
      RETVAL= sb_console_state_restore(cstate);
      cstate->auto_restore= false; /* no longer run restore on destructor */
   OUTPUT:
      RETVAL

bool
wait_char_readable(cstate, timeout_sv=&PL_sv_undef)
   sb_console_state *cstate
   SV *timeout_sv
   CODE:
      RETVAL= sb_console_state_wait_char_readable(aTHX_ cstate, timeout_sv);
   OUTPUT:
      RETVAL

bool
_append_console_char(cstate, buf)
   sb_console_state *cstate
   secret_buffer *buf;
   CODE:
#ifdef WIN32
      RETVAL= sb_append_console_char(aTHX_ cstate, buf);
#else
      RETVAL= false;
#endif
   OUTPUT:
      RETVAL

MODULE = Crypt::SecretBuffer           PACKAGE = Crypt::SecretBuffer::Span

void
new(class_or_obj, ...)
   SV *class_or_obj
   ALIAS:
      clone = 1
      subspan = 2
      Crypt::SecretBuffer::span = 3
      Crypt::SecretBuffer::Exports::span = 4
   INIT:
      secret_buffer_span *span= secret_buffer_span_from_magic(class_or_obj, SECRET_BUFFER_MAGIC_UNDEF_OK);
      SV **buf_field= span && SvTYPE(SvRV(class_or_obj)) == SVt_PVHV
         ? hv_fetchs((HV*)SvRV(class_or_obj), "buf", 0)
         : NULL;
      secret_buffer *buf= secret_buffer_from_magic(
         buf_field? *buf_field : class_or_obj, SECRET_BUFFER_MAGIC_UNDEF_OK
      );
      bool subspan= span && ix >= 2;
      IV base_pos= subspan? span->pos : 0;
      IV pos=0, lim=0, len=0, base_lim=0;
      int encoding= span? span->encoding : 0, i;
      SV *encoding_sv= NULL;
      bool have_pos= false, have_lim= false, have_len= false;
   PPCODE:
      //warn("items=%d  span=%p  buf=%p  base_pos=%d", (int)items, span, buf, (int)base_pos);
      // 3-argument form, only usable when first arg is a buffer or refs a buffer
      if (buf && items >= 2 && looks_like_number(ST(1))) {
         pos= SvIV(ST(1));
         have_pos= true;
         if (items >= 3 && SvOK(ST(2))) {
            len= SvIV(ST(2));
            have_len= true;
            if (items >= 4) {
               encoding_sv= ST(3);
               if (items > 4)
                  warn("unexpected 4th argument after encoding");
            }
         }
      } else { // key => value form
         if ((items - 1) & 1)
            croak("Odd number of arguments; expected (key => value, ...)");
         for (i= 1; i < items-1; i += 2) {
            if (0 == strcmp("pos", SvPV_nolen(ST(i)))) {
               pos= SvIV(ST(i+1));
               have_pos= true;
            }
            else if (0 == strcmp("lim", SvPV_nolen(ST(i)))) {
               lim= SvIV(ST(i+1));
               have_lim= true;
            }
            else if (0 == strcmp("len", SvPV_nolen(ST(i)))) {
               len= SvIV(ST(i+1));
               have_len= true;
            }
            else if (0 == strcmp("encoding", SvPV_nolen(ST(i)))) {
               encoding_sv= ST(i+1);
            }
            else if (0 == strcmp("buf", SvPV_nolen(ST(i)))) {
               buf= secret_buffer_from_magic(ST(i+1), SECRET_BUFFER_MAGIC_OR_DIE);
            }
         }
      }
      if (have_len && have_lim && (lim != pos + len))
         croak("Can't specify both 'len' and 'lim', make up your mind!");
      // buffer is required
      if (!buf) {
         /* The 'span()' exported function can accept plain scalars and upgrade them to a span object */
         if (ix != 4)
            croak("Require 'buf' attribute");
         buf= secret_buffer_new(0, NULL);
         secret_buffer_splice_sv(buf, 0, 0, class_or_obj);
      }
      base_lim= subspan? span->lim : buf->len;
      // pos is relative to base_pos, and needs truncated to (or count backward from) base_lim
      pos= have_pos? normalize_offset(pos, base_lim-base_pos)+base_pos
         : span    ? span->pos
                   : base_pos;
      // likewise for lim, but also might need calculated from 'len'
      lim= have_lim? normalize_offset(lim, base_lim-base_pos)+base_pos
         : have_len? normalize_offset(len, base_lim-pos)+pos
         : span    ? span->lim
                   : base_lim;
      if (pos > lim)
         croak("lim must be greater or equal to pos");
      //warn("  base_lim=%d pos=%d  lim=%d", (int) base_lim, (int)pos, (int)lim);
      // check encoding
      if (encoding_sv) {
         if (!parse_encoding(aTHX_ encoding_sv, &encoding))
            croak("Unknown encoding '%s'", SvPV_nolen(encoding_sv));
      }
      PUSHs(new_mortal_span_obj(aTHX_ buf, pos, lim, encoding));

UV
pos(span, newval_sv= NULL)
   secret_buffer_span *span
   SV *newval_sv
   ALIAS:
      lim = 1
      len = 2
      length = 2
   CODE:
      if (newval_sv) {
         IV newval= SvIV(newval_sv);
         if (newval < 0)
            croak("pos, lim, and len cannot be negative");
         switch (ix) {
         case 0: span->pos= newval; break;
         case 1: if (newval < span->pos) croak("lim must be >= pos");
                 span->lim= newval; break;
         case 2: span->lim= span->pos + newval;
         default: croak("BUG");
         }
      }
      RETVAL= ix == 0? span->pos
            : ix == 1? span->lim
            : ix == 2? span->lim - span->pos
            : -1;
   OUTPUT:
      RETVAL

void
encoding(span, newval_sv= NULL)
   secret_buffer_span *span
   SV *newval_sv
   INIT:
      SV *enc_const;
      AV *encodings= get_av("Crypt::SecretBuffer::_encodings", 0);
      if (!encodings) croak("BUG");
   PPCODE:
      if (newval_sv)
         if (!parse_encoding(aTHX_ newval_sv, &span->encoding))
            croak("Invalid encoding");
      enc_const= *av_fetch(encodings, span->encoding, 1);
      if (!enc_const || !SvOK(enc_const))
         croak("BUG");
      PUSHs(enc_const);

const char *
last_error(span)
   secret_buffer_span *span
   CODE:
      RETVAL= span->last_error;
   OUTPUT:
      RETVAL

void
set_up_us_the_bom(self)
   SV *self
   ALIAS:
      consume_bom = 1
   INIT:
      secret_buffer_span *span= secret_buffer_span_from_magic(self, SECRET_BUFFER_MAGIC_OR_DIE);
      secret_buffer_parse p;
      if (!secret_buffer_parse_init_from_sv(&p, self))
         croak("%s", p.error);
      PERL_UNUSED_VAR(ix);
   PPCODE:
      if (p.lim - p.pos >= 3 && p.pos[0] == 0xEF && p.pos[1] == 0xBB && p.pos[2] == 0xBF) {
         span->encoding= SECRET_BUFFER_ENCODING_UTF8;
         span->pos += 3;
      }
      else if (p.lim - p.pos >= 2 && p.pos[0] == 0xFF && p.pos[1] == 0xFE) {
         span->encoding= SECRET_BUFFER_ENCODING_UTF16LE;
         span->pos += 2;
      }
      else if (p.lim - p.pos >= 2 && p.pos[0] == 0xFE && p.pos[1] == 0xFF) {
         span->encoding= SECRET_BUFFER_ENCODING_UTF16BE;
         span->pos += 2;
      }
      XSRETURN(1);

void
scan(self, pattern=NULL, flags= 0)
   SV *self
   SV *pattern
   IV flags
   ALIAS:
      parse       = 0x102
      rparse      = 0x203
      trim        = 0x322
      ltrim       = 0x422
      rtrim       = 0x523
      starts_with = 0x612
      ends_with   = 0x713
   INIT:
      secret_buffer_span *span= secret_buffer_span_from_magic(self, SECRET_BUFFER_MAGIC_OR_DIE);
      SV **sb_sv= hv_fetchs((HV*)SvRV(self), "buf", 1);
      secret_buffer *buf= secret_buffer_from_magic(*sb_sv, SECRET_BUFFER_MAGIC_OR_DIE);
      secret_buffer_parse parse;
      if (!secret_buffer_parse_init(&parse, buf, span->pos, span->lim, span->encoding))
         croak("%s", parse.error);
      // Bit 0 indicates an op from the end of the buffer
      if (ix & 1)
         flags |= SECRET_BUFFER_MATCH_REVERSE;
      // Bit 1 indicates an anchored op
      if (ix & 2)
         flags |= SECRET_BUFFER_MATCH_ANCHORED;
      // Bits 4..7 indicate return type,
      //   0 == return a span
      //   1 == return bool
      //   2 == return self
      int ret_type= (ix >> 4) & 0xF;
      if (ret_type != 1 && parse.encoding == SECRET_BUFFER_ENCODING_BASE64)
         croak("Cannot perform parse, trim, or scan on base64 (pos / lim of result would not be whole bytes)");
      int op= (ix >> 8);
      bool matched;
      if (!pattern) {
         if (op == 3 || op == 4 || op == 5)
            pattern= get_sv("Crypt::SecretBuffer::Span::default_trim_regex", 0);
         if (!pattern)
            croak("pattern is required");
      }
   PPCODE:
      matched= secret_buffer_match(&parse, pattern, flags);
      if (parse.error)
         croak("%s", parse.error);
      switch (op) {
      case 1: // parse
         if (matched) span->pos= parse.lim - (U8*) buf->data;
         break;
      case 2: // rparse
         if (matched) span->lim= parse.pos - (U8*) buf->data;
         break;
      case 3: // trim
      case 4: // ltrim
         if (matched) span->pos= parse.lim - (U8*) buf->data;
         if (op == 4) break;
         // reset the modified parse_state and run in reverse
         parse.pos= (U8*) (buf->data + span->pos);
         parse.lim= (U8*) (buf->data + span->lim);
         flags |= SECRET_BUFFER_MATCH_REVERSE;
         matched= secret_buffer_match(&parse, pattern, flags);
      case 5: // rtrim, and trim
         if (matched) span->lim= parse.pos - (U8*) buf->data;
         break;
      default:
         (void)0; // suppress warning that not all values were handled
      }
      if (ret_type == 0) {
         if (!matched)
            XSRETURN_UNDEF;
         if (parse.pos > parse.lim || parse.lim > (U8*) buf->data + buf->len)
            croak("BUG: parse pos=%p lim=%p buf.data=%p buf.len=%ld",
               parse.pos, parse.lim, buf->data, (long)buf->len);
         PUSHs(new_mortal_span_obj(aTHX_ buf, parse.pos - (U8*) buf->data, parse.lim - (U8*) buf->data, span->encoding));
      } else if (ret_type == 1) {
         if (matched)
            XSRETURN_YES;
         else
            XSRETURN_NO;
      }
      else {
         XSRETURN(1); // use self pointer in ST(0)
      }

void
parse_lenprefixed(self, count = 1)
   SV *self
   IV count
   INIT:
      secret_buffer_span *span= secret_buffer_span_from_magic(self, SECRET_BUFFER_MAGIC_OR_DIE);
      secret_buffer_parse p;
      UV len;
      size_t ofs;
      /* treat an invalid span as a bug, rather than returning it to the user in the err_out param */
      if (!secret_buffer_parse_init_from_sv(&p, self))
         croak("%s", p.error);
   PPCODE:
      while (count && p.pos < p.lim) {
         if (!secret_buffer_parse_uv_base128be(&p, &len)) {
            span->last_error= p.error;
            XSRETURN_EMPTY;
         }
         if (len > p.lim - p.pos) {
            span->last_error= "Length exceeds end of Span";
            XSRETURN_EMPTY;
         }
         ofs= p.pos - (U8*) p.sbuf->data;
         XPUSHs(secret_buffer_span_new_obj(p.sbuf, ofs, ofs + len, 0));
         p.pos += len;
         if (count > 0) --count;
      }
      span->pos= p.pos - (U8*) p.sbuf->data;
      span->last_error= NULL;

SV*
parse_asn1_der_length(self)
   SV *self
   ALIAS:
      parse_base128le = 1
      parse_base128be = 2
   INIT:
      secret_buffer_span *span= secret_buffer_span_from_magic(self, SECRET_BUFFER_MAGIC_OR_DIE);
      secret_buffer_parse p;
      UV val_out;
      bool success;
      /* treat an invalid span as a bug, rather than returning it to the user as last_error */
      if (!secret_buffer_parse_init_from_sv(&p, self))
         croak("%s", p.error);
   CODE:
      switch (ix) {
      case 0 : success= secret_buffer_parse_uv_asn1_der_length(&p, &val_out); break;
      case 1 : success= secret_buffer_parse_uv_base128le(&p, &val_out); break;
      case 2 : success= secret_buffer_parse_uv_base128be(&p, &val_out); break;
      default: croak("BUG");
      }
      if (success) {
         /* advance the span position */
         span->pos= p.pos - (U8*) p.sbuf->data;
         span->last_error= NULL;
         RETVAL= newSVuv(val_out);
      } else {
         span->last_error= p.error;
         RETVAL= &PL_sv_undef;
      }
   OUTPUT:
      RETVAL

void
copy(self, ...)
   SV *self
   ALIAS:
      copy_to = 1
      append_to = 2
   INIT:
      SV *dst_sv= NULL;
      int next_arg, dst_encoding= -1;
      secret_buffer_parse src;
      if (!secret_buffer_parse_init_from_sv(&src, self))
         croak("%s", src.error);
   PPCODE:
      if (ix > 0) { /* called as 'copy_to' or 'append_to' */
         if (items < 2)
            croak("Missing copy/append destination");
         dst_sv= ST(1);
         next_arg= 2;
      }
      else { /* called as 'copy' */
         secret_buffer_new(0, &dst_sv);
         next_arg= 1;
      }
      
      // parse options
      if ((items - next_arg) & 1)
         croak("expected even-length list of (key => val)");
      for (; next_arg < items; next_arg+= 2) {
         if (0 == strcmp(SvPV_nolen(ST(next_arg)), "encoding")) {
            if (!parse_encoding(aTHX_ ST(next_arg+1), &dst_encoding))
               croak("Unknown encoding");
         }
      }
      if (!secret_buffer_copy_to(&src, dst_sv, dst_encoding, ix == 2))
         croak("copy failed: %s", src.error);
      // copy returns the SecretBuffer, but copy_to returns empty list.
      if (ix == 0)
         PUSHs(dst_sv);

IV
cmp(lhs, rhs, reverse=false)
   SV *lhs
   SV *rhs
   bool reverse
   INIT:
      secret_buffer_parse lhs_parse, rhs_parse;
      if (!secret_buffer_parse_init_from_sv(&lhs_parse, lhs))
         croak("%s", lhs_parse.error);
      if (!secret_buffer_parse_init_from_sv(&rhs_parse, rhs))
         croak("%s", rhs_parse.error);
   CODE:
      RETVAL= sb_parse_codepointcmp(&lhs_parse, &rhs_parse);
      if (reverse)
         RETVAL= -RETVAL;
   OUTPUT:
      RETVAL

BOOT:
   int i;
   HV *stash= gv_stashpvs("Crypt::SecretBuffer", 1);
   HV *exports_stash= gv_stashpvs("Crypt::SecretBuffer::Exports", 1);
#define EXPORT_CONST(name, const) \
   newCONSTSUB(stash, name, make_enum_dualvar(aTHX_ const, newSVpvs_share(name)));\
   hv_stores(exports_stash, name, SvREFCNT_inc(*hv_fetchs(stash, name, 0)))
   EXPORT_CONST("NONBLOCK",      SECRET_BUFFER_NONBLOCK);
   EXPORT_CONST("AT_LEAST",      SECRET_BUFFER_AT_LEAST);
   EXPORT_CONST("MATCH_MULTI",   SECRET_BUFFER_MATCH_MULTI);
   EXPORT_CONST("MATCH_REVERSE", SECRET_BUFFER_MATCH_REVERSE);
   EXPORT_CONST("MATCH_NEGATE",  SECRET_BUFFER_MATCH_NEGATE);
   EXPORT_CONST("MATCH_ANCHORED",SECRET_BUFFER_MATCH_ANCHORED);
   EXPORT_CONST("MATCH_CONST_TIME",SECRET_BUFFER_MATCH_CONST_TIME);
#undef EXPORT_CONST
   SV *enc[SECRET_BUFFER_ENCODING_MAX+1];



( run in 0.937 second using v1.01-cache-2.11-cpan-71847e10f99 )