This is Info file pm.info, produced by Makeinfo version 1.68 from the input file bigpm.texi.  File: pm.info, Node: ControlX10/CM17, Next: Convert/ASN1, Prev: ControlX10/CM11, Up: Module List Perl extension for 'FireCracker' RF Transmitter *********************************************** NAME ==== ControlX10::CM17 - Perl extension for 'FireCracker' RF Transmitter SYNOPSIS ======== use ControlX10::CM17; # $serial_port is an object created using Win32::SerialPort # or Device::SerialPort depending on OS # my $serial_port = setup_serial_port('COM10', 4800); &ControlX10::CM17::send($serial_port, 'A1J'); # Turns device A1 On &ControlX10::CM17::send($serial_port, 'A1K'); # Turns device A1 Off &ControlX10::CM17::send($serial_port, 'BO'); # Turns All lights on house code B off DESCRIPTION =========== The FireCracker (CM17A) is a send-only X10 controller that connects to a serial port and transmits commands via RF to X10 transceivers. The FireCracker derives its power supply from either the RTS or DTR signals from the serial port. At least one of these signals must be high at all times to ensure that power is not lost from the FireCracker. The signals are pulsed to transmit a bit (DTR for '1' and RTS for '0'). The normal rx/tx read/write lines are not used by the device - but are passed through to allow another serial device to be connected (as long as it does not require hardware handshaking). A 40-bit command packet consists of a constant 16 bit header, a constant 8 bit footer, and 16 data bits. The data is subdivided into a 5 bit address *$house* code (A-P) and an 11 bit *$operation*. There are "ON" commands for 16 units per *$house* code (1J, 2J...FJ, GJ) and similar "OFF" commands (1K, 2K...FK, GK). A send decodes a parameter string that combines *$house$operation* into a single instruction. In addition to *$operation* commands that act on individual units, there are some that apply to the entire *$house* code or to previous commands. $operation FUNCTION L Brighten Last Light Programmed 14% M Dim Last Light Programmed 14% N All Lights Off O All Lights On P All Units Off Starting with Version 0.6, a series of Brighten or Dim Commands may be combined into a single *$operation* by specifying a signed amount of change desired after the unit code. An "ON" command will be sent to select the unit followed by at least one Brighten/Dim. The value will round to the next larger magnitude if not a multiple of 14%. &ControlX10::CM17::send($serial_port, 'A3-10'); # outputs 'A3J','AM' - at least one dim &ControlX10::CM17::send($serial_port, 'A3-42'); # outputs 'A3J','AM','AM','AM' - even multiple of 14 &ControlX10::CM17::send($serial_port, 'AF-45'); # outputs 'AFJ','AL','AL','AL','AL' - round up if remainer EXPORTS ======= The *send_cm17* method is exported by default. It is identical to `&ControlX10::CM17::send()', and accepts the same parameters. use ControlX10::CM17; send_cm17($serial_port, 'A1J'); AUTHORS ======= Bruce Winter bruce@misterhouse.net http://misterhouse.net CPAN packaging by Bill Birthisel wcbirthisel@alum.mit.edu http://members.aol.com/bbirthisel MAILING LISTS ------------- General information about the mailing lists is at: http://lists.sourceforge.net/mailman/listinfo/misterhouse-users http://lists.sourceforge.net/mailman/listinfo/misterhouse-announce To post to this list, send your email to: misterhouse-users@lists.sourceforge.net If you ever want to unsubscribe or change your options (eg, switch to or from digest mode, change your password, etc.), visit your subscription page at: http://lists.sourceforge.net/mailman/options/misterhouse-users/$user_id SEE ALSO ======== mh can be download from http://misterhouse.net You can subscribe to the mailing list at http://www.onelist.com/subscribe.cgi/misterhouse You can view the mailing list archive at http://www.onelist.com/archives.cgi/misterhouse perl(1). Win32::SerialPort and Device::SerialPort COPYRIGHT ========= Copyright (C) 2000 Bruce Winter. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. 30 January 2000.  File: pm.info, Node: Convert/ASN1, Next: Convert/BER, Prev: ControlX10/CM17, Up: Module List ASN.1 Encode/Decode library *************************** NAME ==== Convert::ASN1 - ASN.1 Encode/Decode library SYNOPSYS ======== use Convert::ASN1; $asn = Convert::ASN1->new; $asn->prepare(q< [APPLICATION 7] SEQUENCE { int INTEGER, str OCTET STRING } >); $pdu = $asn->encode( int => 7, str => "string"); $out = $asn->decode($pdu); print $out->{int}," ",$out->{str},"\n"; use Convert::ASN1 qw(:io); $peer = asn_recv($sock,$buffer,0); $nbytes = asn_read($fh, $buffer); $nbytes = asn_send($sock, $buffer, $peer); $nbytes = asn_send($sock, $buffer); $nbytes = asn_write($fh, $buffer); $buffer = asn_get($fh); $yes = asn_ready($fh) DESCRIPTION =========== Convert::ASN1 encodes and decodes ASN.1 data structures using BER/DER rules. METHODS ======= new --- Contructor, creates a new object. error ----- Returns the last error. configure ( OPTIONS ) --------------------- Configure options to control how Convert::ASN1 will perform various tasks. Options are passed as name-value pairs. encode Reference to a hash which contains various encode options. decode Reference to a hash which contains various decode options. encoding One of 'ber', 'der', 'per'. *Currently not used* Encode options real Which encoding to use for real's. One of 'binary', 'nr1', 'nr2', 'nr3' time This controls how UTCTime and GeneralizedTime elements are encoded. The default is withzone. utctime The value passed will be encoded without a zone, ie a UTC value. withzone The value will be encoded with a zone. By default it will be encoded using the local time offset. The offset may be set using the timezone configure option. raw The value passed should already be in the correct format and will be copied into the PDU as-is. timezone By default UTCTime and GeneralizedTime will be encoded using the local time offset from UTC. This will over-ride that. It is an offset from UTC in seconds. This option can be overriden by passing a reference to a list of two values as the time value. The list should contain the time value and the offset from UTC in seconds. Decode options time This controls how a UTCTime or a GeneralizedTime element will be decoded. The default is utctime. utctime The value returned will be a time value as returned by the time function. withzone The value returned will be a reference to an array of two values. The first is the same as with utctime, the second is the timezone offset, in seconds, that was used in the encoding. raw The value returned will be the raw encoding as extracted from the PDU. prepare ( ASN ) --------------- Compile the given ASN.1 descripton. The syntax used is very close to ASN.1, but has a few differnces. If the ASN decribes only one macro then encode/decode can be called on this object. If ASN describes more than one ASN.1 macro then find must be called. find ( MACRO ) -------------- Find a macro froma prepared ASN.1 description. Returns an object which can be used for encode/decode. encode ( VARIABLES ) -------------------- Encode a PDU. Top-level variable are passed as name-value pairs, or as a reference to a hash containing them. Returns the encoded PDU, or undef on error. decode ( PDU ) -------------- Decode the PDU, returns a reference to a hash containg the values for the PDU. Returns undef if there was an error. EXPORTS ======= As well as providing an object interface for encoding/decoding PDUs Convert::ASN1 also provides the follow functions. IO Functions ------------ asn_recv SOCK, BUFFER, FLAGS Will read a single element from the socket SOCK into BUFFER. FLAGS may be MSG_PEEK as exported by Socket. Returns the address of the sender, or undef if there was an error. Some systems do not support the return of the peer address when the socket is a connected socket, in these cases the empty string will be returned. This is the same behaviour as the recv function in perl itself. It is reccomended that if the socket is of type SOCK_DGRAM then recv be called directly instead of calling asn_recv. asn_read FH, BUFFER, OFFSET asn_read FH, BUFFER Will read a single element from the filehandle FH into BUFFER. Returns the number of bytes read if a complete element was read, -1 if an incomplete element was read or undef if there was an error. If OFFSET is specified then it is assumed that BUFFER already contains an incomplete element and new data will be appended starting at OFFSET. If FH is a socket the asn_recv is used to read the element, so the same restiction applies if FH is a socket of type SOCK_DGRAM. asn_send SOCK, BUFFER, FLAGS, TO asn_send SOCK, BUFFER, FLAGS Identical to calling send, see *Note Perlfunc: (perl.info)perlfunc, asn_write FH, BUFFER Identical to calling syswrite with 2 arguments, see *Note Perlfunc: (perl.info)perlfunc, asn_get FH asn_get provides buffered IO. Because it needs a buffer FH must be a GLOB or a reference to a GLOB. asn_get will use two entries in the hash element of the GLOB to use as it's buffer asn_buffer - input buffer asn_need - number of bytes needed for the next element, if known Returns an element or undef if there was an error. asn_ready FH asn_ready works with asn_get. It will return true if asn_get has already read enough data into the buffer to return a complete element. Encode/Decode Functions ----------------------- asn_tag asn_decode_tag asn_encode_tag asn_decode_length asn_encode_length Constants --------- ASN_BIT_STR ASN_BOOLEAN ASN_ENUMERATED ASN_GENERAL_TIME ASN_IA5_STR ASN_INTEGER ASN_NULL ASN_OBJECT_ID ASN_OCTET_STR ASN_PRINT_STR ASN_REAL ASN_SEQUENCE ASN_SET ASN_UTC_TIME ASN_APPLICATION ASN_CONTEXT ASN_PRIVATE ASN_UNIVERSAL ASN_PRIMITIVE ASN_CONSTRUCTOR ASN_LONG_LEN ASN_EXTENSION_ID ASN_BIT Debug Functions --------------- asn_dump asn_hexdump EXPORT TAGS =========== :all All exported functions :const ASN_BOOLEAN, ASN_INTEGER, ASN_BIT_STR, ASN_OCTET_STR, ASN_NULL, ASN_OBJECT_ID, ASN_REAL, ASN_ENUMERATED, ASN_SEQUENCE, ASN_SET, ASN_PRINT_STR, ASN_IA5_STR, ASN_UTC_TIME, ASN_GENERAL_TIME, ASN_UNIVERSAL, ASN_APPLICATION, ASN_CONTEXT, ASN_PRIVATE, ASN_PRIMITIVE, ASN_CONSTRUCTOR, ASN_LONG_LEN, ASN_EXTENSION_ID, ASN_BIT :debug asn_dump, asn_dumphex :io asn_recv, asn_send, asn_read, asn_write, asn_get, asn_ready :tag asn_tag, asn_decode_tag, asn_encode_tag, asn_decode_length, asn_encode_length MAPPING ASN.1 TO PERL ===================== Every element in the ASN.1 definition has a name, in perl a hash is used with these names as an index and the element value as the hash value. # ASN.1 int INTEGER, str OCTET STRING # Perl { int => 5, str => "text" } In the case of a SEQUENCE, SET or CHOICE then the value in the namespace will be a hash reference which will be the namespce for the elements with that element. # ASN.1 int INTEGER, seq SEQUENCE { str OCTET STRING, bool BOOLEAN } # Perl { int => 5, seq => { str => "text", bool => 1}} If the element is a SEQUENCE OF, or SET OF, then the value in the namespace will be an array reference. The elements in the array will be of the type expected by the type following the OF. For example with "SEQUENCE OF STRING" the array would contain strings. With "SEQUENCE OF SEQUENCE { ... }" the array will contain hash references which will be used as namespaces # ASN.1 int INTEGER, str SEQUENCE OF OCTET STRING # Perl { int => 5, str => [ "text1", "text2"]} # ASN.1 int INTEGER, str SEQUENCE OF SEQUENCE { type OCTET STRING, value INTEGER } # Perl { int => 5, str => [ { type => "abc", value => 4 }, { type => "def", value => -1 }, ]} Exceptions ---------- There are some exceptions where Convert::ASN1 does not require an element to be named. These are SEQUENCE {...}, SET {...} and CHOICE. In each case if the element is not given a name then the elements inside the {...} will share the same namespace as the elements outside of the {...}. TODO ==== * Decoding of a SET. * Indefinite length encoding * XS implementation. * More documentation. * More tests. AUTHOR ====== Graham Barr COPYRIGHT ========= Copyright (c) 2000 Graham Barr . All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.  File: pm.info, Node: Convert/BER, Next: Convert/Base32, Prev: Convert/ASN1, Up: Module List ASN.1 Basic Encoding Rules ************************** NAME ==== Convert::BER - ASN.1 Basic Encoding Rules SYNOPSIS ======== use Convert::BER; $ber = new Convert::BER; $ber->encode( INTEGER => 1, SEQUENCE => [ BOOLEAN => 0, STRING => "Hello", ], REAL => 3.7, ); $ber->decode( INTEGER => \$i, SEQUENCE => [ BOOLEAN => \$b, STRING => \$s, ], REAL => \$r, ); DESCRIPTION =========== Convert::BER provides an OO interface to encoding and decoding data using the ASN.1 Basic Encoding Rules (BER), a platform independent way of encoding structured binary data together with the structure. METHODS ======= new new ( BUFFER ) new ( opList ) new creates a new Convert::BER object. encode ( opList ) Encode data in *opList* appending to the data in the buffer. decode ( opList ) Decode the data in the buffer as described by *opList*, starting where the last decode finished or position set by pos. buffer ( [ BUFFER ] ) Return the buffer contents. If *BUFFER* is specified set the buffer contents and reset pos to zero. pos ( [ POS ] ) Without any arguments pos returns the offset where the last decode finished, or the last offset set by pos. If *POS* is specified then *POS* will be where the next decode starts. tag ( ) Returns the tag at the current position in the buffer. length ( ) Returns the length of the buffer. error ( ) Returns the error message associated with the last method, if any. This value is not automatically reset. If encode or decode returns undef, check this. dump ( [ FH ] ) Dump the buffer to the filehandle FH, or STDERR if not specified. The output contains the hex dump of each element, and an ASN.1-like text representation of that element. hexdump ( [ FH ] ) Dump the buffer to the filehandle FH, or STDERR if not specified. The output is hex with the possibly-printable text alongside. IO METHODS ========== read ( IO ) write ( IO ) recv ( SOCK ) send ( SOCK [, ADDR ] ) OPLIST ====== An *opList* is a list of operator-value pairs. An operator can be any of those defined below, or any defined by sub-classing Convert::BER, which will probably be derived from the primitives given here. The values depend on whether BER is being encoded or decoded: Encoding If the value is a scalar, just encode it. If the value is a reference to a list, then encode each item in the list in turn. If the value is a code reference, then execute the code. If the returned value is a scalar, encode that value. If the returned value is a reference to a list, encode each item in the list in turn. Decoding If the value is a reference to a scalar, decode the value into the scalar. If the value is a reference to a list, then decode all the items of this type into the list. Note that there must be at least one item to decode, otherwise the decode will fail. If the value is a code reference, then execute the code and decode the value into the reference returned from the evaluated code. PRIMITIVE OPERATORS =================== These operators encode and decode the basic primitive types defined by BER. BOOLEAN ------- A BOOLEAN value is either true or false. Encoding The value is tested for boolean truth, and encoded appropriately. # Encode a TRUE value $ber->encode( BOOLEAN => 1, ) or die; Decoding The decoded values will be either 1 or 0. # Decode a boolean value into $bval $ber->decode( BOOLEAN => \$bval, ) or die; INTEGER ------- An INTEGER value is either a positive whole number, or a negative whole number, or zero. Numbers can either be native perl integers, or values of the Math::BigInt class. Encoding The value is the integer value to be encoded. $ber->encode( INTEGER => -123456, ) or die; Decoding The value will be the decoded integer value. $ber->decode( INTEGER => \$ival, ) or die; STRING ------ This is an OCTET STRING, which is an arbitrarily long binary value. Encoding The value contains the binary value to be encoded. $ber->encode( STRING => "\xC0First character is hex C0", ) or die; Decoding The value will be the binary bytes. $ber->decode( STRING => \$sval, ) or die; NULL ---- There is no value for NULL. You often use NULL in ASN.1 when you want to denote that something else is absent rather than just not encoding the 'something else'. Encoding The values are ignored, but must be present. $ber->encode( NULL => undef, ) or die; Decoding Dummy values are stored in the returned values, as though they were present in the encoding. $ber->decode( NULL => \$nval, ) or die; OBJECT_ID --------- An OBJECT_ID value is an OBJECT IDENTIFIER (also called an OID). This is a hierarchically structured value that is used in protocols to uniquely identify something. For example, SNMP (the Simple Network Management Protocol) uses OIDs to denote the information being requested, and LDAP (the Lightweight Directory Access Protocol, RFC 2251) uses OIDs to denote each attribute in a directory entry. Each level of the OID hierarchy is either zero or a positive integer. Encoding The value should be a dotted-decimal representation of the OID. $ber->encode( OBJECT_ID => '2.5.4.0', # LDAP objectClass ) or die; Decoding The value will be the dotted-decimal representation of the OID. $ber->decode( OBJECT_ID => \$oval, ) or die; ENUM ---- The ENUMERATED type is effectively the same as the INTEGER type. It exists so that friendly names can be assigned to certain integer values. To be useful, you should sub-class this operator. BIT_STRING ---------- The BIT STRING type is an arbitrarily long string of bits - 0's and 1's. Encoding The value is a string of arbitrary 0 and 1 characters. As these are packed into 8-bit octets when encoding and there may not be a multiple of 8 bits to be encoded, trailing padding bits are added in the encoding. $ber->encode( BIT_STRING => '0011', ) or die; Decoding The value will be a string of 0 and 1 characters. The string will have the same number of bits as were encoded (the padding bits are ignored.) $ber->decode( BIT_STRING => \$bval, ) or die; BIT_STRING8 ----------- This is a variation of the BIT_STRING operator, which is optimized for writing bit strings which are multiples of 8-bits in length. You can use the BIT_STRING operator to decode BER encoded with the BIT_STRING8 operator (and vice-versa.) Encoding The value should be the packed bits to encode, not a string of 0 and 1 characters. $ber->encode( BIT_STRING8 => pack('B8', '10110101'), ) or die; Decoding The value will be the decoded packed bits. $ber->decode( BIT_STRING8 => \$bval, ) or die; REAL ---- The REAL type encodes an floating-point number. It requires the POSIX module. Encoding The value should be the number to encode. $ber->encode( REAL => 3.14159265358979, ) or die; Decoding The value will be the decoded floating-point value. $ber->decode( REAL => \$rval, ); ObjectDescriptor ---------------- The ObjectDescriptor type encodes an ObjectDescriptor string. It is a sub-class of STRING. UTF8String ---------- The UTF8String type encodes a string encoded in UTF-8. It is a sub-class of STRING. NumericString ------------- The NumericString type encodes a NumericString, which is defined to only contain the characters 0-9 and space. It is a sub-class of STRING. PrintableString --------------- The PrintableString type encodes a PrintableString, which is defined to only contain the characters A-Z, a-z, 0-9, space, and the punctuation characters ()-+=:',./?. It is a sub-class of STRING. TeletexString/T61String ----------------------- The TeletexString type encodes a TeletexString, which is a string containing characters according to the T.61 character set. Each T.61 character may be one or more bytes wide. It is a sub-class of STRING. T61String is an alternative name for TeletexString. VideotexString -------------- The VideotexString type encodes a VideotexString, which is a string. It is a sub-class of STRING. IA5String --------- The IA5String type encodes an IA5String. IA5 (International Alphabet 5) is equivalent to US-ASCII. It is a sub-class of STRING. UTCTime ------- The UTCTime type encodes a UTCTime value. Note this value only represents years using two digits, so it is not recommended in Y2K-compliant applications. It is a sub-class of STRING. UTCTime values must be strings like: yymmddHHMM[SS]Z or: yymmddHHMM[SS]sHHMM Where yy is the year, mm is the month (01-12), dd is the day (01-31), HH is the hour (00-23), MM is the minutes (00-60). SS is the optional seconds (00-61). The time is either terminated by the literal character Z, or a timezone offset. The "Z" character indicates Zulu time or UTC. The timezone offset specifies the sign s, which is + or -, and the difference in hours and minutes. GeneralizedTime --------------- The GeneralizedTime type encodes a GeneralizedTime value. Unlike UTCTime it represents years using 4 digits, so is Y2K-compliant. It is a sub-class of STRING. GeneralizedTime values must be strings like: yyyymmddHHMM[SS][.U][Z] or: yyyymmddHHMM[SS][.U]sHHMM Where yyyy is the year, mm is the month (01-12), dd is the day (01-31), HH is the hour (00-23), MM is the minutes (00-60). SS is the optional seconds (00-61). U is the optional fractional seconds value; a comma is permitted instead of a dot before this value. The time may be terminated by the literal character Z, or a timezone offset. The "Z" character indicates Zulu time or UTC. The timezone offset specifies the sign s, which is + or -, and the difference in hours and minutes. If there is timezone specified UTC is assumed. GraphicString ------------- The GraphicString type encodes a GraphicString value. It is a sub-class of STRING. VisibleString/ISO646String -------------------------- The VisibleString type encodes a VisibleString value, which is a value using the ISO646 character set. It is a sub-class of STRING. ISO646String is an alternative name for VisibleString. GeneralString ------------- The GeneralString type encodes a GeneralString value. It is a sub-class of STRING. UniversalString/CharacterString ------------------------------- The UniveralString type encodes a UniveralString value, which is a value using the ISO10646 character set. Each character in ISO10646 is 4-bytes wide. It is a sub-class of STRING. CharacterString is an alternative name for UniversalString. BMPString --------- The BMPString type encodes a BMPString value, which is a value using the Unicode character set. Each character in the Unicode character set is 2-bytes wide. It is a sub-class of STRING. CONSTRUCTED OPERATORS ===================== These operators are used to build constructed types, which contain values in different types, like a C structure. SEQUENCE -------- A SEQUENCE is a complex type that contains other types, a bit like a C structure. Elements inside a SEQUENCE are encoded and decoded in the order given. Encoding The value should be a reference to an array containing another *opList* which defines the elements inside the SEQUENCE. $ber->encode( SEQUENCE => [ INTEGER => 123, BOOLEAN => [ 1, 0 ], ] ) or die; Decoding The value should a reference to an array that contains the *opList* which decodes the contents of the SEQUENCE. $ber->decode( SEQUENCE => [ INTEGER => \$ival, BOOLEAN => \@bvals, ] ) or die; SET --- A SET is an complex type that contains other types, rather like a SEQUENCE. Elements inside a SET may be present in any order. Encoding The value is the same as for the SEQUENCE operator. $ber->encode( SET => [ INTEGER => 13, STRING => 'Hello', ] ) or die; Decoding The value should be a reference to an *equivalent* *opList* to that used to encode the SET. The ordering of the *opList* should not matter. $ber->decode( SET => [ STRING => \$sval, INTEGER => \$ival, ] ) or die; SEQUENCE_OF ----------- A SEQUENCE_OF is an ordered list of other types. Encoding The value is a ref followed by an *opList*. The ref must be a reference to a list or a hash: if it is to a list, then the *opList* will be repeated once for every element in the list. If it is to a hash, then the *opList* will be repeated once for every key in the hash (note that ordering of keys in a hash is not guaranteed by perl.) The remaining *opList* will then usually contain values which are code references. If the ref is to a list, then the contents of that item in the list are passed as the only argument to the code reference. If the ref is to a hash, then only the key is passed to the code. @vals = ( [ 10, 'Foo' ], [ 20, 'Bar' ] ); # List of refs to lists $ber->encode( SEQUENCE_OF => [ \@vals, SEQUENCE => [ INTEGER => sub { $_[0][0] }, # Passed a ref to the inner list STRING => sub { $_[0][1] }, # Passed a ref to the inner list ] ] ) or die; %hash = ( 40 => 'Baz', 30 => 'Bletch' ); # Just a hash $ber->decode( SEQUENCE_OF => [ \%hash, SEQUENCE => [ INTEGER => sub { $_[0] }, # Passed the key STRING => sub { $hash{$_[0]} }, # Passed the key ] ] ); Decoding The value must be a reference to a list containing a ref and an *opList*. The ref must always be a reference to a scalar. Each value in the is usually a code reference. The code referenced is called with the value of the ref (dereferenced); the value of the ref is incremented for each item in the SEQUENCE_OF. $ber->decode( SEQUENCE_OF => [ \$count, # In the following subs, make space at the end of an array, and # return a reference to that newly created space. SEQUENCE => [ INTEGER => sub { $ival[$_[0]] = undef; \$ival[-1] }, STRING => sub { $sval[$_[0]] = undef; \$sval[-1] }, ] ] ) or die; SET_OF ------ A SET_OF is an unordered list. This is treated in an identical way to a SEQUENCE_OF, except that no ordering should be inferred from the list passed or returned. SPECIAL OPERATORS ================= BER --- It is sometimes useful to construct or deconstruct BER encodings in several pieces. The BER operator lets you do this. Encoding The value should be another Convert::BER object, which will be inserted into the buffer. If value is undefined then nothing is added. $tmp->encode( SEQUENCE => [ INTEGER => 20, STRING => 'Foo', ] ); $ber->encode( BER => $tmp, BOOLEAN => 1 ); Decoding value should be a reference to a scalar, which will contain a Convert::BER object. This object will contain the remainder of the current sequence or set being decoded. # After this, ber2 will contain the encoded INTEGER B STRING. # sval will be ignored and left undefined, but bval will be decoded. The # decode of ber2 will return the integer and string values. $ber->decode( SEQUENCE => [ BER => \$ber2, STRING => \$sval, ], BOOLEAN => \$bval, ); $ber2->decode( INTEGER => \$ival, STRING => \$sval2, ); ANY --- This is like the BER operator except that when decoding only the next item is decoded and placed into the Convert::BER object returned. There is no difference when encoding. Decoding value should be a reference to a scalar, which will contain a Convert::BER object. This object will only contain the next single item in the current sequence being decoded. # After this, ber2 will decode further, and ival and sval # will be decoded. $ber->decode( INTEGER = \$ival, ANY => \$ber2, STRING => \$sval, ); OPTIONAL -------- This operator allows you to specify that an element is absent from the encoding. Encoding The value should be a reference to another list with another *opList*. If all of the values of the inner *opList* are defined, the entire OPTIONAL value will be encoded, otherwise it will be omitted. $ber->encode( SEQUENCE => [ INTEGER => 16, # Will be encoded OPTIONAL => [ INTEGER => undef, # Will not be encoded ], STRING => 'Foo', # Will be encoded ] ); Decoding The contents of value are decoded if possible, if not then decode continues at the next operator-value pair. $ber->decode( SEQUENCE => [ INTEGER => \$ival1, OPTIONAL => [ INTEGER => \$ival2, ], STRING => \$sval, ] ); CHOICE ------ The *opList* is a list of alternate operator-value pairs. Only one will be encoded, and only one will be decoded. Encoding A scalar at the start of the *opList* identifies which *opList* alternative to use for encoding the value. A value of 0 means the first one is used, 1 means the second one, etc. # Encode the BMPString alternate of the CHOICE $ber->encode( CHOICE => [ 2, PrintableString => 'Printable', TeletexString => 'Teletex/T61', BMPString => 'BMP/Unicode', UniversalString => 'Universal/ISO10646', ] ) or die; Decoding A reference to a scalar at the start of the *opList* is used to store which alternative is decoded (0 for the first one, 1 for the second one, etc.) Pass undef instead of the ref if you don't care about this, or you store all the alternate values in different variables. # Decode the above. # Afterwards, $alt will be set to 2, $str will be set to 'BMP/Unicode'. $ber->decode( CHOICE => [ \$alt, PrintableString => \$str, TeletexString => \$str, BMPString => \$str, UniversalString => \$str, ] ) or die; TAGS ==== In BER everything being encoded has a tag, a length, and a value. Normally the tag is derived from the operator - so INTEGER has a different tag from a BOOLEAN, for instance. In some applications it is necessary to change the tags used. For example, a SET may need to contain two different INTEGER values. Tags may be changed in two ways, either IMPLICITly or EXPLICITly. With IMPLICIT tagging, the new tag completely replaces the old tag. With EXPLICIT tagging, the new tag is used *as well as* the old tag. Convert::BER supports two ways of using IMPLICIT tagging. One method is to sub-class Convert::BER, which is described in the next section. For small applications or those that think sub-classing is just too much then the operator may be passed an arrayref. The array must contain two elements, the first is the usual operator name and the second is the tag value to use, as shown below. $ber->encode( [ SEQUENCE => 0x34 ] => [ INTEGER => 10, STRING => "A" ] ) or die; This will encode a sequence, with a tag value of `0x34', which will contain and integer and a string which will have their default tag values. You may wish to construct your tags using some pre-defined functions such as `&Convert::BER::BER_APPLICATION', `&Convert::BER::BER_CONTEXT', etc, instead of calculating the tag values yourself. To use EXPLICIT tagging, enclose the original element in a SEQUENCE, and just override the SEQUENCE's tag as above. Don't forget to set the constructed bit using `&Convert::BER::BER_CONSTRUCTOR'. For example, the ASN.1 definition: Foo ::= SEQUENCE { [0] EXPLICIT INTEGER, INTEGER } might be encoded using this: $ber->encode( SEQUENCE => [ [ SEQUENCE => &Convert::BER::BER_CONTEXT | &Convert::BER::BER_CONSTRUCTOR | 0 ] => [ INTEGER => 10, ], INTEGER => 11, ], ) or die; SUB-CLASSING ============ For large applications where operators with non default tags are used a lot the above mechanism can be very error-prone. For this reason, Convert::BER may be sub-classed. To do this the sub-class must call a static method define. The arguments to define is a list of arrayrefs. Each arrayref will define one new operator. Each arrayref contains three values, the first is the name of the operator, the second is how the data is encoded and the third is the tag value. To aid with the creation of these arguments Convert::BER exports some variables and constant subroutines. For each operator defined by Convert::BER, or a Convert::BER sub-class, a scalar variable with the same name is available for import, for example `$INTEGER' is available from Convert::BER. And any operators defined by a new sub-class will be available for import from that class. One of these variables may be used as the second element of each arrayref. Convert::BER also exports some constant subroutines that can be used to create the tag value. The subroutines exported are: BER_BOOLEAN BER_INTEGER BER_BIT_STR BER_OCTET_STR BER_NULL BER_OBJECT_ID BER_SEQUENCE BER_SET BER_UNIVERSAL BER_APPLICATION BER_CONTEXT BER_PRIVATE BER_PRIMITIVE BER_CONSTRUCTOR Convert::BER also provides a subroutine called `ber_tag' to calculate an integer value that will be used to represent a tag. For tags with values less than 30 this is not needed, but for tags >= 30 then tag value passed for an operator definition must be the result of `ber_tag' `ber_tag' takes two arguments, the first is the tag class and the second is the tag value. Using this information a sub-class of Convert::BER can be created as shown below. package Net::LDAP::BER; use Convert::BER qw(/^(\$|BER_)/); use strict; use vars qw($VERSION @ISA); @ISA = qw(Convert::BER); $VERSION = "1.00"; Net::LDAP::BER->define( # Name Type Tag ######################################## [ REQ_UNBIND => $NULL, BER_APPLICATION | 0x02 ], [ REQ_COMPARE => $SEQUENCE, BER_APPLICATION | BER_CONSTRUCTOR | 0x0E ], [ REQ_ABANDON => $INTEGER, ber_tag(BER_APPLICATION, 0x10) ], ); This will create a new class `Net::LDAP::BER' which has three new operators available. This class then may be used as follows $ber = new Net::LDAP::BER; $ber->encode( REQ_UNBIND => 0, REQ_COMPARE => [ REQ_ABANDON => 123, ] ); $ber->decode( REQ_UNBIND => \$var, REQ_COMPARE => [ REQ_ABANDON => \$num, ] ); Which will encode or decode the data using the formats and tags defined in the `Net::LDAP::BER' sub-class. It also helps to make the code more readable. DEFINING NEW PACKING OPERATORS ------------------------------ As well as defining new operators which inherit from existing operators it is also possible to define a new operator and how data is encoded and decoded. The interface for doing this is still changing but will be documented here when it is done. To be continued ... LIMITATIONS =========== Convert::BER cannot support tags that contain more bits than can be stored in a scalar variable, typically this is 32 bits. Convert::BER cannot support items that have a packed length which cannot be stored in 32 bits. BUGS ==== The SET decode method fails if the encoded order is different to the *opList* order. AUTHOR ====== Graham Barr Significant POD updates from Chris Ridd COPYRIGHT ========= Copyright (c) 1995-2000 Graham Barr. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.  File: pm.info, Node: Convert/Base32, Next: Convert/BinHex, Prev: Convert/BER, Up: Module List Encoding and decoding of base32 strings *************************************** NAME ==== Convert::Base32 - Encoding and decoding of base32 strings SYNOPSIS ======== use Convert::Base32 qw(encode_base32 decode_base32); $encoded = encode_base32("\x3a\x27\x0f\x93"); $decoded = decode_base32($encoded); DESCRIPTION =========== This module provides functions to convert string from / to Base32 encoding, specified in RACE internet-draft. The Base32 encoding is designed to encode non-ASCII characters in DNS-compatible host name parts. See http://www.ietf.org/internet-drafts/draft-ietf-idn-race-03.txt for more details. FUNCTIONS ========= Following functions are provided; unlike `MIME::Base64', they are in *@EXPORT_OK* array. See *Note Exporter: Exporter, for details. encode_base32($str) Encode data by calling the encode_base32() function. This function takes a string to encode and returns the encoded base32 string. decode_base32($str) Decode a base32 string by calling the decode_base32() function. This function takes a string to decode and returns the decoded string. This function might throw the exceptions such as "Data contains non-base32 characters", "Length of data invalid" and "PADDING number of bits at the end of output buffer are not all zero". TODO ==== Using XS would be by far efficient. AUTHOR ====== Tatsuhiko Miyagawa This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. SEE ALSO ======== http://www.ietf.org/internet-drafts/draft-ietf-idn-race-03.txt, *Note MIME/Base64: MIME/Base64,, *Note Convert/RACE: Convert/RACE,.  File: pm.info, Node: Convert/BinHex, Next: Convert/Context, Prev: Convert/Base32, Up: Module List extract data from Macintosh BinHex files **************************************** NAME ==== Convert::BinHex - extract data from Macintosh BinHex files *ALPHA WARNING: this code is currently in its Alpha release. Things may change drastically until the interface is hammered out: if you have suggestions or objections, please speak up now!* SYNOPSIS ======== *Simple functions:* use Convert::BinHex qw(binhex_crc macbinary_crc); # Compute HQX7-style CRC for data, pumping in old CRC if desired: $crc = binhex_crc($data, $crc); # Compute the MacBinary-II-style CRC for the data: $crc = macbinary_crc($data, $crc); *Hex to bin, low-level interface.* Conversion is actually done via an object (`"Convert::BinHex::Hex2Bin"' in this node) which keeps internal conversion state: # Create and use a "translator" object: my $H2B = Convert::BinHex->hex2bin; # get a converter object while () { print $STDOUT $H2B->next($_); # convert some more input } print $STDOUT $H2B->done; # no more input: finish up *Hex to bin, OO interface.* The following operations must be done in the order shown! # Read data in piecemeal: $HQX = Convert::BinHex->open(FH=>\*STDIN) || die "open: $!"; $HQX->read_header; # read header info @data = $HQX->read_data; # read in all the data @rsrc = $HQX->read_resource; # read in all the resource *Bin to hex, low-level interface.* Conversion is actually done via an object (`"Convert::BinHex::Bin2Hex"' in this node) which keeps internal conversion state: # Create and use a "translator" object: my $B2H = Convert::BinHex->bin2hex; # get a converter object while () { print $STDOUT $B2H->next($_); # convert some more input } print $STDOUT $B2H->done; # no more input: finish up *Bin to hex, file interface.* Yes, you can convert to BinHex as well as from it! # Create new, empty object: my $HQX = Convert::BinHex->new; # Set header attributes: $HQX->filename("logo.gif"); $HQX->type("GIFA"); $HQX->creator("CNVS"); # Give it the data and resource forks (either can be absent): $HQX->data(Path => "/path/to/data"); # here, data is on disk $HQX->resource(Data => $resourcefork); # here, resource is in core # Output as a BinHex stream, complete with leading comment: $HQX->encode(\*STDOUT); *PLANNED!!!! Bin to hex, "CAP" interface.* *Thanks to Ken Lunde for suggesting this*. # Create new, empty object from CAP tree: my $HQX = Convert::BinHex->from_cap("/path/to/root/file"); $HQX->encode(\*STDOUT); DESCRIPTION =========== BinHex is a format used by Macintosh for transporting Mac files safely through electronic mail, as short-lined, 7-bit, semi-compressed data streams. Ths module provides a means of converting those data streams back into into binary data. FORMAT ====== *(Some text taken from RFC-1741.)* Files on the Macintosh consist of two parts, called *forks*: Data fork The actual data included in the file. The Data fork is typically the only meaningful part of a Macintosh file on a non-Macintosh computer system. For example, if a Macintosh user wants to send a file of data to a user on an IBM-PC, she would only send the Data fork. Resource fork Contains a collection of arbitrary attribute/value pairs, including program segments, icon bitmaps, and parametric values. Additional information regarding Macintosh files is stored by the Finder in a hidden file, called the "Desktop Database". Because of the complications in storing different parts of a Macintosh file in a non-Macintosh filesystem that only handles consecutive data in one part, it is common to convert the Macintosh file into some other format before transferring it over the network. The BinHex format squashes that data into transmittable ASCII as follows: 1. The file is output as a *byte stream* consisting of some basic header information (filename, type, creator), then the data fork, then the resource fork. 2. The byte stream is *compressed* by looking for series of duplicated bytes and representing them using a special binary escape sequence (of course, any occurences of the escape character must also be escaped). 3. The compressed stream is *encoded* via the "6/8 hemiola" common to base64 and *uuencode*: each group of three 8-bit bytes (24 bits) is chopped into four 6-bit numbers, which are used as indexes into an ASCII "alphabet". (I assume that leftover bytes are zero-padded; documentation is thin). FUNCTIONS ========= CRC computation --------------- macbinary_crc DATA, SEED Compute the MacBinary-II-style CRC for the given DATA, with the CRC seeded to SEED. Normally, you start with a SEED of 0, and you pump in the previous CRC as the SEED if you're handling a lot of data one chunk at a time. That is: $crc = 0; while () { $crc = macbinary_crc($_, $crc); } Note: Extracted from the *mcvert* utility (Doug Moore, April '87), using a "magic array" algorithm by Jim Van Verth for efficiency. Converted to Perl5 by Eryq. *Untested.* binhex_crc DATA, SEED Compute the HQX-style CRC for the given DATA, with the CRC seeded to SEED. Normally, you start with a SEED of 0, and you pump in the previous CRC as the SEED if you're handling a lot of data one chunk at a time. That is: $crc = 0; while () { $crc = binhex_crc($_, $crc); } Note: Extracted from the *mcvert* utility (Doug Moore, April '87), using a "magic array" algorithm by Jim Van Verth for efficiency. Converted to Perl5 by Eryq. OO INTERFACE ============ Conversion ---------- bin2hex *Class method, constructor.* Return a converter object. Just creates a new instance of `"Convert::BinHex::Bin2Hex"' in this node; see that class for details. hex2bin *Class method, constructor.* Return a converter object. Just creates a new instance of `"Convert::BinHex::Hex2Bin"' in this node; see that class for details. Construction ------------ new PARAMHASH *Class method, constructor.* Return a handle on a BinHex'able entity. In general, the data and resource forks for such an entity are stored in native format (binary) format. Parameters in the PARAMHASH are the same as header-oriented method names, and may be used to set attributes: $HQX = new Convert::BinHex filename => "icon.gif", type => "GIFB", creator => "CNVS"; open PARAMHASH *Class method, constructor.* Return a handle on a new BinHex'ed stream, for parsing. Params are: Data Input a HEX stream from the given data. This can be a scalar, or a reference to an array of scalars. Expr Input a HEX stream from any open()able expression. It will be opened and binmode'd, and the filehandle will be closed either on a close() or when the object is destructed. FH Input a HEX stream from the given filehandle. NoComment If true, the parser should not attempt to skip a leading "(This file...)" comment. That means that the first nonwhite characters encountered must be the binhex'ed data. Get/set header information -------------------------- creator [VALUE] *Instance method.* Get/set the creator of the file. This is a four-character string (though I don't know if it's guaranteed to be printable ASCII!) that serves as part of the Macintosh's version of a MIME "content-type". For example, a document created by "Canvas" might have creator `"CNVS"'. data [PARAMHASH] *Instance method.* Get/set the data fork. Any arguments are passed into the new() method of `"Convert::BinHex::Fork"' in this node. filename [VALUE] *Instance method.* Get/set the name of the file. flags [VALUE] *Instance method.* Return the flags, as an integer. Use bitmasking to get as the values you need. header_as_string Return a stringified version of the header that you might use for logging/debugging purposes. It looks like this: X-HQX-Software: BinHex 4.0 (Convert::BinHex 1.102) X-HQX-Filename: Something_new.eps X-HQX-Version: 0 X-HQX-Type: EPSF X-HQX-Creator: ART5 X-HQX-Data-Length: 49731 X-HQX-Rsrc-Length: 23096 As some of you might have guessed, this is RFC-822-style, and may be easily plunked down into the middle of a mail header, or split into lines, etc. requires [VALUE] *Instance method.* Get/set the software version required to convert this file, as extracted from the comment that preceded the actual binhex'ed data; e.g.: (This file must be converted with BinHex 4.0) In this case, after parsing in the comment, the code: $HQX->requires; would get back "4.0". resource [PARAMHASH] *Instance method.* Get/set the resource fork. Any arguments are passed into the new() method of `"Convert::BinHex::Fork"' in this node. type [VALUE] *Instance method.* Get/set the type of the file. This is a four-character string (though I don't know if it's guaranteed to be printable ASCII!) that serves as part of the Macintosh's version of a MIME "content-type". For example, a GIF89a file might have type `"GF89"'. version [VALUE] *Instance method.* Get/set the version, as an integer. Decode, high-level ------------------ read_comment *Instance method.* Skip past the opening comment in the file, which is of the form: (This file must be converted with BinHex 4.0) As per RFC-1741, *this comment must immediately precede the BinHex data,* and any text before it will be ignored. *You don't need to invoke this method yourself;* `read_header()' will do it for you. After the call, the version number in the comment is accessible via the requires() method. read_header *Instance method.* Read in the BinHex file header. You must do this first! read_data [NBYTES] *Instance method.* Read information from the data fork. Use it in an array context to slurp all the data into an array of scalars: @data = $HQX->read_data; Or use it in a scalar context to get the data piecemeal: while (defined($data = $HQX->read_data)) { # do stuff with $data } The NBYTES to read defaults to 2048. read_resource [NBYTES] *Instance method.* Read in all/some of the resource fork. See `read_data()' for usage. Encode, high-level ------------------ encode OUT Encode the object as a BinHex stream to the given output handle OUT. OUT can be a filehandle, or any blessed object that responds to a print() message. The leading comment is output, using the requires() attribute. SUBMODULES ========== Convert::BinHex::Bin2Hex ------------------------ A BINary-to-HEX converter. This kind of conversion requires a certain amount of state information; it cannot be done by just calling a simple function repeatedly. Use it like this: # Create and use a "translator" object: my $B2H = Convert::BinHex->bin2hex; # get a converter object while () { print STDOUT $B2H->next($_); # convert some more input } print STDOUT $B2H->done; # no more input: finish up # Re-use the object: $B2H->rewind; # ready for more action! while () { ... On each iteration, next() (and `done()') may return either a decent-sized non-empty string (indicating that more converted data is ready for you) or an empty string (indicating that the converter is waiting to amass more input in its private buffers before handing you more stuff to output. Note that `done()' always converts and hands you whatever is left. This may have been a good approach. It may not. Someday, the converter may also allow you give it an object that responds to read(), or a FileHandle, and it will do all the nasty buffer-filling on its own, serving you stuff line by line: # Someday, maybe... my $B2H = Convert::BinHex->bin2hex(\*STDIN); while (defined($_ = $B2H->getline)) { print STDOUT $_; } Someday, maybe. Feel free to voice your opinions. Convert::BinHex::Hex2Bin ------------------------ A HEX-to-BINary converter. This kind of conversion requires a certain amount of state information; it cannot be done by just calling a simple function repeatedly. Use it like this: # Create and use a "translator" object: my $H2B = Convert::BinHex->hex2bin; # get a converter object while () { print STDOUT $H2B->next($_); # convert some more input } print STDOUT $H2B->done; # no more input: finish up # Re-use the object: $H2B->rewind; # ready for more action! while () { ... On each iteration, next() (and `done()') may return either a decent-sized non-empty string (indicating that more converted data is ready for you) or an empty string (indicating that the converter is waiting to amass more input in its private buffers before handing you more stuff to output. Note that `done()' always converts and hands you whatever is left. Note that this converter does not find the initial "BinHex version" comment. You have to skip that yourself. It only handles data between the opening and closing `":"'. Convert::BinHex::Fork --------------------- A fork in a Macintosh file. # How to get them... $data_fork = $HQX->data; # get the data fork $rsrc_fork = $HQX->resource; # get the resource fork # Make a new fork: $FORK = Convert::BinHex::Fork->new(Path => "/tmp/file.data"); $FORK = Convert::BinHex::Fork->new(Data => $scalar); $FORK = Convert::BinHex::Fork->new(Data => \@array_of_scalars); # Get/set the length of the data fork: $len = $FORK->length; $FORK->length(170); # this overrides the REAL value: be careful! # Get/set the path to the underlying data (if in a disk file): $path = $FORK->path; $FORK->path("/tmp/file.data"); # Get/set the in-core data itself, which may be a scalar or an arrayref: $data = $FORK->data; $FORK->data($scalar); $FORK->data(\@array_of_scalars); # Get/set the CRC: $crc = $FORK->crc; $FORK->crc($crc); UNDER THE HOOD ============== Design issues ------------- BinHex needs a stateful parser Unlike its cousins base64 and *uuencode*, BinHex format is not amenable to being parsed line-by-line. There appears to be no guarantee that lines contain 4n encoded characters... and even if there is one, the BinHex compression algorithm interferes: even when you can decode one line at a time, you can't necessarily *decompress* a line at a time. For example: a decoded line ending with the byte `\x90' (the escape or "mark" character) is ambiguous: depending on the next decoded byte, it could mean a literal `\x90' (if the next byte is a `\x00'), or it could mean n-1 more repetitions of the previous character (if the next byte is some nonzero n). For this reason, a BinHex parser has to be somewhat stateful: you cannot have code like this: #### NO! #### NO! #### NO! #### NO! #### NO! #### while () { # read HEX print hexbin($_); # convert and write BIN } unless something is happening "behind the scenes" to keep track of what was last done. *The dangerous thing, however, is that this approach will *seem* to work, if you only test it on BinHex files which do not use compression and which have 4n HEX characters on each line.* Since we have to be stateful anyway, we use the parser object to keep our state. We need to be handle large input files Solutions that demand reading everything into core don't cut it in my book. The first MPEG file that comes along can louse up your whole day. So, there are no size limitations in this module: the data is read on-demand, and filehandles are always an option. Boy, is this slow! A lot of the byte-level manipulation that has to go on, particularly the CRC computing (which involves intensive bit-shifting and masking) slows this module down significantly. What is needed perhaps is an optional extension library where the slow pieces can be done more quickly... a Convert::BinHex::CRC, if you will. Volunteers, anyone? Even considering that, however, it's slower than I'd like. I'm sure many improvements can be made in the HEX-to-BIN end of things. No doubt I'll attempt some as time goes on... How it works ------------ Since BinHex is a layered format, consisting of... A Macintosh file [the "BIN"]... Encoded as a structured 8-bit bytestream, then... Compressed to reduce duplicate bytes, then... Encoded as 7-bit ASCII [the "HEX"] ...there is a layered parsing algorithm to reverse the process. Basically, it works in a similar fashion to stdio's fread(): 0. There is an internal buffer of decompressed (BIN) data, initially empty. 1. Application asks to read() n bytes of data from object 2. If the buffer is not full enough to accomodate the request: 2a. The read() method grabs the next available chunk of input data (the HEX). 2b. HEX data is converted and decompressed into as many BIN bytes as possible. 2c. BIN bytes are added to the read() buffer. 2d. Go back to step 2a. until the buffer is full enough or we hit end-of-input. The conversion-and-decompression algorithms need their own internal buffers and state (since the next input chunk may not contain all the data needed for a complete conversion/decompression operation). These are maintained in the object, so parsing two different input streams simultaneously is possible. WARNINGS ======== Only handles `Hqx7' files, as per RFC-1741. Remember that Macintosh text files use `"\r"' as end-of-line: this means that if you want a textual file to look normal on a non-Mac system, you probably want to do this to the data: # Get the data, and output it according to normal conventions: foreach ($HQX->read_data) { s/\r/\n/g; print } CHANGE LOG ========== Current version: $Id: BinHex.pm,v 1.119 1997/06/28 05:12:42 eryq Exp $ Version 1.118 Ready to go public (with Paul's version, patched for native Mac support)! Warnings have been suppressed in a few places where undefined values appear. Version 1.115 Fixed another bug in comp2bin, related to the MARK falling on a boundary between inputs. Added testing code. Version 1.114 Added BIN-to-HEX conversion. Eh. It's a start. Also, a lot of documentation additions and cleanups. Some methods were also renamed. Version 1.103 Fixed bug in decompression (wasn't saving last character). Fixed "NoComment" bug. Version 1.102 Initial release. AUTHOR AND CREDITS ================== Written by Eryq, `http://www.enteract.com/~eryq' / `eryq@enteract.com' Support for native-Mac conversion, plus invaluable contributions in Alpha Testing, plus a few patches, plus the baseline binhex/debinhex programs, were provided by Paul J. Schinder (NASA/GSFC). Ken Lunde (Adobe) suggested incorporating the CAP file representation. TERMS AND CONDITIONS ==================== Copyright (c) 1997 by Eryq. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. This software comes with NO WARRANTY of any kind. See the COPYING file in the distribution for details.