package Convert::ASN1;
use strict;
use Socket;
BEGIN {
local $SIG{__DIE__};
eval { require bytes } and 'bytes'->import
}
sub asn_recv {
my $peer;
my $buf;
my $n = 128;
my $pos = 0;
my $depth = 0;
my $len = 0;
my($tmp,$tb,$lb);
MORE:
for(
$peer = recv($_[0],$buf,$n,MSG_PEEK);
defined $peer;
$peer = recv($_[0],$buf,$n<<=1,MSG_PEEK)
) {
if ($depth) {
unless (2+$pos <= length $buf) {
next MORE if $n == length $buf;
last MORE;
}
if(substr($buf,$pos,2) eq "\0\0") {
unless (--$depth) {
$len = $pos + 2;
last MORE;
}
}
}
($tb,$tmp) = asn_decode_tag(substr($buf,$pos));
unless ($tb || $pos+$tb < length $buf) {
next MORE if $n == length $buf;
last MORE;
}
if (ord(substr($buf,$pos+$tb,1)) == 0x80) {
$depth++;
$pos += $tb + 1;
redo MORE;
}
($lb,$len) = asn_decode_length(substr($buf,$pos+$tb));
if ($lb) {
if ($depth) {
$pos += $tb + $lb + $len;
redo MORE;
}
else {
$len += $tb + $lb + $pos;
last MORE;
}
}
}
if (defined $peer) {
if ($len > length $buf) {
goto error
unless defined($peer = recv($_[0],$buf,$len,MSG_PEEK));
if ($len > length $buf) {
$_[1]='';
return $peer;
}
}
elsif ($len == 0) {
$_[1] = '';
return $peer;
}
if ($_[2] & MSG_PEEK) {
$_[1] = substr($buf,0,$len);
}
elsif (!defined($peer = recv($_[0],$_[1],$len,0))) {
goto error;
}
return $peer;
}
error:
$_[1] = undef;
}
sub asn_read {
if ($_[2]) {
if ($_[2] > length $_[1]) {
require Carp;
Carp::carp("Offset beyond end of buffer");
return;
}
substr($_[1],$_[2]) = '';
}
else {
$_[1] = '';
}
my $pos = 0;
my $need = 0;
my $depth = 0;
my $ch;
my $n;
my $e;
while(1) {
$need = ($pos + ($depth * 2)) || 2;
while(($n = $need - length $_[1]) > 0) {
$e = sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
my $tch = ord(substr($_[1],$pos++,1));
if(($tch & 0x1f) == 0x1f) {
my $ch;
do {
$need++;
while(($n = $need - length $_[1]) > 0) {
$e = sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
$ch = ord(substr($_[1],$pos++,1));
} while($ch & 0x80);
}
$need = $pos + 1;
while(($n = $need - length $_[1]) > 0) {
$e = sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
my $len = ord(substr($_[1],$pos++,1));
if($len & 0x80) {
unless ($len &= 0x7f) {
$depth++;
next;
}
$need = $pos + $len;
while(($n = $need - length $_[1]) > 0) {
$e = sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
$pos += $len + unpack("N", "\0" x (4 - $len) . substr($_[1],$pos,$len));
}
elsif (!$len && !$tch) {
die "Bad ASN PDU" unless $depth;
unless (--$depth) {
last;
}
}
else {
$pos += $len;
}
last unless $depth;
}
while(($n = $pos - length $_[1]) > 0) {
$e = sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
return length $_[1];
READ_ERR:
$@ = defined($e) ? "Unexpected EOF" : "I/O Error $!"; return undef;
}
sub asn_send {
@_ == 4
? send($_[0],$_[1],$_[2],$_[3])
: send($_[0],$_[1],$_[2]);
}
sub asn_write {
syswrite($_[0],$_[1], length $_[1]);
}
sub asn_get {
my $fh = ref($_[0]) ? $_[0] : \($_[0]);
my $href = \%{*$fh};
$href->{'asn_buffer'} = '' unless exists $href->{'asn_buffer'};
my $need = delete $href->{'asn_need'} || 0;
while(1) {
next if $need;
my($tb,$tag) = asn_decode_tag($href->{'asn_buffer'}) or next;
my($lb,$len) = asn_decode_length(substr($href->{'asn_buffer'},$tb,8)) or next;
$need = $tb + $lb + $len;
}
continue {
if ($need && $need <= length $href->{'asn_buffer'}) {
my $ret = substr($href->{'asn_buffer'},0,$need);
substr($href->{'asn_buffer'},0,$need) = '';
return $ret;
}
my $get = $need > 1024 ? $need : 1024;
sysread($_[0], $href->{'asn_buffer'}, $get, length $href->{'asn_buffer'})
or return undef;
}
}
sub asn_ready {
my $fh = ref($_[0]) ? $_[0] : \($_[0]);
my $href = \%{*$fh};
return 0 unless exists $href->{'asn_buffer'};
return $href->{'asn_need'} <= length $href->{'asn_buffer'}
if exists $href->{'asn_need'};
my($tb,$tag) = asn_decode_tag($href->{'asn_buffer'}) or return 0;
my($lb,$len) = asn_decode_length(substr($href->{'asn_buffer'},$tb,8)) or return 0;
$href->{'asn_need'} = $tb + $lb + $len;
$href->{'asn_need'} <= length $href->{'asn_buffer'};
}
1;