/*
* fortopat.c --
*
* Implementation of format string to xsd pattern conversion.
*
* Copyright (c) 2001 J. Schoenwaelder, Technical University of Braunschweig.
* (c) 2002 T. Klie, Technical University of Braunschweig.
* (c) 2002 F. Strauss, Technical University of Braunschweig.
* (c) 2007 T. Klie, Technical University of Braunschweig.
* (c) 2007 J. Schoenwaelder, Jacobs University Bremen.
*
* See the file "COPYING" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
* @(#) $Id: dump-xsd.c 7372 2007-10-17 15:26:40Z tklie $
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#ifdef HAVE_WIN_H
#include "win.h"
#endif
#include "smi.h"
#include "smidump.h"
#include "fortopat.h"
#ifndef MIN
#define MIN(a,b) ((a)) < ((b)) ? ((a)) : ((b))
#endif
/* definition of commonly used display hints */
#define LIBSMI_DH_TP_IPV4Z_ADDR "1d.1d.1d.1d%4d:2d"
#define LIBSMI_DH_UDP_IPV4 "1d.1d.1d.1d/2d"
#define LIBSMI_DH_IPV4Z_ADDR "1d.1d.1d.1d%4d"
#define LIBSMI_DH_IPV4_ADDR "1d.1d.1d.1d"
#define LIBSMI_DH_LDP_ID "1d.1d.1d.1d:2d"
#define LIBSMI_DH_IPV6_ADDR "2x:2x:2x:2x:2x:2x:2x:2x"
#define LIBSMI_DH_IPV6Z_ADDR "2x:2x:2x:2x:2x:2x:2x:2x%4d"
#define LIBSMI_DH_TP_IPV6_ADDR "0a[2x:2x:2x:2x:2x:2x:2x:2x]0a:2d"
#define LIBSMI_DH_TP_IPV6Z_ADDR "0a[2x:2x:2x:2x:2x:2x:2x:2x%4d]0a:2d"
#define LIBSMI_DH_HEX "1x"
#define LIBSMI_DH_HEX_COLON "1x:"
#define LIBSMI_DH_DOUBLE_HEX_COLON "2x:"
#define LIBSMI_DH_ASCII "255a"
#define LIBSMI_DH_UTF8 "255t"
#define LIBSMI_DH_ASCII_CHAR "1a"
#define LIBSMI_DH_DATETIME "2d-1d-1d,1d.1d.1d,1a1d:1d"
#define LIBSMI_DH_DATETIME2 "2d-1d-1d,1d:1d:1d.1d,1a1d:1d"
#define LIBSMI_DH_IPX_ADDR "4x.1x:1x:1x:1x:1x:1x.2d"
/* list of parts of a display hint */
typedef struct DH {
unsigned int number;
/* int repeat; // repeat not yet supported */
char type;
char separator[3];
/* char repTerm; // repeat not yet supported */
struct DH *next;
} DH;
static int
smiPow(int base, unsigned int exponent)
{
unsigned int i;
int ret = 1;
if (exponent == 0) {
return 1;
}
for (i = 0; i < exponent; i++) {
ret *= base;
}
return ret;
}
static unsigned int
numDigits(unsigned int val)
{
int ret = 1;
for (; val / 10; val = val / 10) {
ret++;
}
return ret;
}
static int
getBracketLevel(char *bracketString)
{
int level = 0;
char *c = bracketString;
while(level >= 0 && *c != '\0') {
switch(*c) {
case '(':
level++;
break;
case ')':
level--;
break;
default:
break;
}
c++;
}
return level;
}
/* parse a display hint and create a list of DHs */
static struct DH*
parseDH(const char *hint)
{
struct DH *iterDH = (struct DH *)malloc( sizeof( struct DH ) );
struct DH *ret = iterDH;
struct DH *oldIterDH = iterDH;
unsigned int pos = 0;
if (!ret) {
return NULL;
}
memset(iterDH, 0, sizeof(struct DH));
while( pos < strlen( hint ) ) {
if( ! iterDH ) {
iterDH = (struct DH *)malloc( sizeof( struct DH ) );
if( ! iterDH ) return NULL;
memset(iterDH, 0, sizeof(struct DH));
oldIterDH->next = iterDH;
oldIterDH = iterDH;
}
switch( hint[ pos ] ) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
unsigned int endPos;
char *strNum;
/* find end of number */
for( endPos = pos; isdigit( hint[ endPos ] ); endPos++ );
/* parse number */
strNum = (char *)malloc( endPos - pos );
strncpy( strNum, &hint[ pos ], endPos - pos );
strNum[ endPos - pos ] = '\0';
iterDH->number = atoi( strNum );
free( strNum );
/* forward the position pointer */
pos = endPos;
break;
}
case 'a':
case 'b':
case 'd':
case 'o':
case 't':
case 'x':
iterDH->type = hint[ pos++ ];
if( isdigit( hint[ pos ] ) || hint[ pos ] == '*' ) {
iterDH = iterDH->next;
}
break;
case '*':
/* repeat not supported */
return NULL;
default:
if( iterDH->separator ) {
/* iterDH->repTerm = hint[ pos++ ]; // repeat not supported */
pos++;
}
else {
iterDH->separator[0] = hint[ pos++ ];
if (iterDH->separator[0] == '.' ) {
iterDH->separator[0] = '\\';
iterDH->separator[1] = '.';
}
}
if( isdigit( hint[ pos ] ) || hint[ pos ] == '*' ) {
iterDH = iterDH->next;
}
break;
}
}
return ret;
}
static char*
getSimpleCharFacet_XSD(char dhType, unsigned int number,
unsigned int minL, unsigned int maxL)
{
char *ret;
char *baseRegExp;
/* determine base regular expression, depending on display hint type */
switch( dhType ) {
case 'a':
baseRegExp = "\\p{IsBasicLatin}";
break;
case 't':
baseRegExp = ".";
break;
default:
/* XXX - fix this to use the normal error reporting mechanism XXX */
fprintf(stderr,
"displayHint.c: Error: unknown display hint type\n");
return NULL;
}
smiAsprintf( &ret, "%s{%d,%d}", baseRegExp, minL, MIN( number, maxL));
return ret;
}
/* check if the hint is known and (if so) return the XSD translation */
static char*
getKnownDisplayHint_XSD(const char *hint,
SmiUnsigned32 *lengths, unsigned int numSubranges)
{
int i;
char *ret = "";
if( ! strcmp( hint, LIBSMI_DH_TP_IPV4Z_ADDR ) )
/* this is a simplyfied restriction.
* We could restrict more,
* but then, the pattern will be very hard to read / understand */
return xstrdup("(\\d{1,3}.){3}\\d{1,3})%\\d{1,10}:\\d{1,5}");
if( ! strcmp( hint, LIBSMI_DH_UDP_IPV4 ) )
return xstrdup("(\\d{1,3}.){3}\\d{1,3}/\\d{1,5}");
if( ! strcmp( hint, LIBSMI_DH_IPV4Z_ADDR ) )
return xstrdup("(\\d{1,3}.){3}\\d{1,3}%\\d{1,10}");
if( ! strcmp( hint, LIBSMI_DH_IPV4_ADDR ) )
return xstrdup("(\\d{1,3}.){3}\\d{1,3}");
if( ! strcmp( hint, LIBSMI_DH_LDP_ID ) )
return xstrdup("(\\d{1,3}.){3}\\d{1,3}:\\d{1,5}");
if( ! strcmp( hint, LIBSMI_DH_IPV6_ADDR ) )
return xstrdup("([\\dA-Fa-f]{2}:){7}[\\dA-Fa-f]{2}");
if( ! strcmp( hint, LIBSMI_DH_IPV6Z_ADDR ) )
return xstrdup("([\\dA-Fa-f]{2}:){7}[\\dA-Fa-f]{2}%\\d{1,10}");
if( ! strcmp( hint, LIBSMI_DH_TP_IPV6_ADDR ) )
return xstrdup("\\[([\\dA-Fa-f]{2}:){7}[\\dA-Fa-f]{2}\\]:\\d{1,5}");
if( ! strcmp( hint, LIBSMI_DH_TP_IPV6Z_ADDR ) )
return xstrdup("\\[([\\dA-Fa-f]{2}:){7}[\\dA-Fa-f]{2}%\\d{1,10}\\]:\\d{1,5}");
if( ! strcmp( hint, LIBSMI_DH_HEX ) )
return xstrdup("[0-9A-Fa-f]{2}");
if( ! strcmp( hint, LIBSMI_DH_HEX_COLON ) )
return xstrdup("[0-9A-Fa-f]{2}:");
if( ! strcmp( hint, LIBSMI_DH_DOUBLE_HEX_COLON ) )
return xstrdup("[0-9A-Fa-f]{4}:");
if( ! strcmp( hint, LIBSMI_DH_ASCII ) ) {
switch( numSubranges ) {
case 0:
return getSimpleCharFacet_XSD( 'a', 255, 0, 255 );
case 1:
return getSimpleCharFacet_XSD( 'a', 255, lengths[0], lengths[1] );
default:
ret = "(";
for( i=0; i < numSubranges * 2; i+=2 ) {
if( i ) smiAsprintf(&ret, "%s)|(", ret);
smiAsprintf(&ret, "%s%s", ret,
getSimpleCharFacet_XSD('a', 255,
lengths[i], lengths[i+1]));
}
smiAsprintf(&ret, "%s)", ret);
return ret;
}
}
if( ! strcmp(hint, LIBSMI_DH_UTF8 )) {
switch( numSubranges ) {
case 0:
return getSimpleCharFacet_XSD( 't' , 255, 0, 255 );
case 1:
return getSimpleCharFacet_XSD( 't' , 255, lengths[0], lengths[1]);
default:
ret = "(";
for( i=0; i < numSubranges * 2; i+=2 ) {
if( i ) smiAsprintf( &ret, "%s)|(", ret );
smiAsprintf(&ret, "%s%s",
ret, getSimpleCharFacet_XSD('t', 255,
lengths[i],
lengths[i+1]));
}
smiAsprintf( &ret, "%s)", ret );
return ret;
}
}
if( ! strcmp( hint, LIBSMI_DH_ASCII_CHAR ))
return xstrdup("\\p{IsBasicLatin}{0,1}");
/* we assume here date-time-value, i.e. the number of digits are fixed */
if( ! strcmp( hint, LIBSMI_DH_DATETIME ) )
return xstrdup("\\d{4}-\\d{2}-\\d{2},(\\d{2}.){2}\\d{2},\\p{IsBasicLatin}\\d{2}:\\d{2}");
if( ! strcmp( hint, LIBSMI_DH_DATETIME2 ) ) {
if( numSubranges >1 )
/* there are more subranges,
* assume: lengths = 8 | lengths = 11
* thus: last 3 octets (time zone info) is optional */
return xstrdup("\\d{4}-\\d{2}-\\d{2},(\\d{2}:){2}\\d{2}(,[\\+\\-]\\d{2}:\\d{2}){0,1}");
return xstrdup("\\d{4}-\\d{2}-\\d{2},(\\d{2}:){2}\\d{2},[\\+\\-]\\d{2}:\\d{2}");
}
if( !strcmp( hint, LIBSMI_DH_IPX_ADDR ) )
return xstrdup("[0-9A-Fa-f]{8}.([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}.\\d{1,4}");
else return NULL;
}
/* build a regexp pattern from a display hint
*
* Some documentation: First of all, it is checked whether the display hint
* matches againts one of the well known display hints. If so, a direct mapping
* is applied. Note that most cases ignore any additional length information
* (except DATETIME and ASCII / UTF8).
* If no match can be found, the display hint is parsed and seperated
* into a list of blocks (DH struct). A display hint block consists of
* o the number of octets to be used (unsigned int "number"),
* o a charakter representing the type (char "type"),
* o the separator (char[3] "separator"),
* o a pointer to the next display hint block in the list.
*
* Then, it is checked whether it is a "simple" display hint,
* i.e. is hint that consists only of a single block and
* ASCII / UTF-8 restriction (type 'a' or 't'; as in 100a, 127t).
* (Maybe this can be extended for other simple hints.)
*
* If all of the above matches fail, the hints blocks are converted
* step by step using the these Mappings:
* o ASCII: \p{IsBasicLatin}
* o UTF-8: .
* o Binary: (0|1){8} (8 digits (0 or 1))
* o Decimal: [0-9]{3} (3 decimal digits)
* o Octal: [0-7]{3} (3 octal digits)
* o Hexadecimal: [0-9A-Fa-f]{2} (2 hexadecimal digits)
* The conversion is not straight forward,
* because there may be also (even several) length restrictions
* and we have to count (or try to estimate) the used octets. Thus,
* it is sometimes necessary to generate multiple patterns
* with different length specification.
*
* Unfortunately, the conversion still is quite difficult to understand
* although I added some comments. The good news is, that all IETF MIBs
* (and all other MIBs coming with libsmi) can be converted "the easy way"
* (i.e. without the difficult conversion loop).*/
char* getStrDHType(const char *hint,
SmiUnsigned32 *lengths, unsigned int numSubranges)
{
unsigned int i = 0;
char *ret = lengths[ i ] ? "(" : "((";
char *r2 = getKnownDisplayHint_XSD( hint, lengths, numSubranges );
DH *dh;
int bl;
r2 = NULL;
if( r2 ) {
/* if( lengths[ i ] && lengths[i+1] < 65535 ) {
fputs( "LENGTH: ", stdout );
fprintf( stdout, "min: %d, max: %d ; ", lengths[i], lengths[i+1] );
fprintf( stdout, "hint was: %s\n", hint );
}
if( numSubranges > 1 ) fputs( "MORE_SUBRANGES\n", stdout );*/
return r2;
}
dh = parseDH( hint );
bl = 0;
if(! dh ) return NULL;
/* check if we have a "simple" display hint (e.g. "100a") */
if( !dh->next && ( dh->type == 'a' || dh->type == 't' ) ) {
switch( numSubranges ) {
case 0:
return getSimpleCharFacet_XSD( dh->type , dh->number, 0, dh->number );
case 1:
return getSimpleCharFacet_XSD( dh->type , dh->number, lengths[0], lengths[1]);
default:
ret = "(";
for( i=0; i < numSubranges * 2; i+=2 ) {
if( i ) smiAsprintf( &ret, "%s)|(", ret );
smiAsprintf( &ret, "%s%s",
ret, getSimpleCharFacet_XSD(dh->type, dh->number,
lengths[i],
lengths[i+1]));
}
smiAsprintf(&ret, "%s)", ret);
return ret;
}
}
/* no "easy match was possible, so start the "hard" loop */
do {
unsigned int octetsUsed = 0;
DH *iterDH;
for( iterDH = dh; iterDH; iterDH = iterDH->next ) {
char *baseRegexp = NULL;
switch( iterDH->type ) {
case 'a':
/* ascii character */
baseRegexp = "(\\p{IsBasicLatin})";
break;
case 't':
/* utf-8 character */
baseRegexp = ".";
break;
case 'b':
/* binary number */
baseRegexp = "((0|1){8})";
break;
case 'd':
/* decimal number */
baseRegexp = "([0-9]{3})";
break;
case 'o':
/* octal number */
baseRegexp = "([0-7]{3})";
break;
case 'x':
/* hexadecimal number */
baseRegexp = "([0-9A-Fa-f]{2})";
break;
default:
fputs( "smidump: Warning: unknown type of display-hint",
stderr );
}
if( iterDH->number < lengths[ i ] ) {
/* there are more octets to come */
if( iterDH->type == 'd' ) {
/* decimal number needs to be treated differently */
if( iterDH->next ){
/* we still have another diplay hint block coming */
smiAsprintf( &ret, "%s(0|[1-9](([0-9]){0,%d}))",
ret,
numDigits(smiPow(255,
iterDH->number ))-1 );
/* adjust number of used digits */
octetsUsed += iterDH->number;
if( octetsUsed >= lengths[ i + 1 ] ) {
/* maximum number of octets used,
we must exit the loop */
break;
}
/* add separator char */
if( iterDH->separator ) {
smiAsprintf( &ret, "%s%s",ret, iterDH->separator );
}
}
else {
/* no orther display hint coming up.
* we are at the last iteration */
smiAsprintf( &ret, "%s((0|[1-9](([0-9]){0,%d})",
ret,
numDigits( smiPow( 255,
iterDH->number ) ) - 1 );
/* add separator char */
if( iterDH->separator ) {
smiAsprintf( &ret, "%s%s",
ret, iterDH->separator );
}
if( lengths[ i+1 ] - 1 - octetsUsed ) {
/* not all digits for maximum string length (length[i+1 ])
* have been used, so we have to add some digits */
smiAsprintf(&ret,
"%s){%u,%u})(0|[1-9](([0-9]){0,%d}))",
ret, lengths[ i ] - 1 - octetsUsed,
lengths[ i+1 ] - 1 - octetsUsed,
numDigits(
smiPow( 255, iterDH->number ))- 1 );
}
else {
/* maximum number of digets have been used,
* so let's terminate the pattern for this round*/
smiAsprintf( &ret, "%s)", ret );
}
/* adjust the used digit counter */
octetsUsed += iterDH->number;
if( octetsUsed >= lengths[ i + 1 ] ) {
/* maximum number of octets used, we must exit the loop */
break;
}
}
}
else {
/* type other than decimal */
if( iterDH->next ){
/* there will be another display hint block */
smiAsprintf( &ret, "%s(%s{%d})",
ret,
baseRegexp, iterDH->number );
/* adjust number of used octets */
octetsUsed += iterDH->number;
if( octetsUsed >= lengths[ i + 1 ] ) {
/* maximum number of octets used,
we must exit the loop */
break;
}
/* add separator char */
if( iterDH->separator ) {
smiAsprintf( &ret, "%s%s", ret, iterDH->separator );
}
}
else {
/* we are the last display hint block */
smiAsprintf( &ret, "%s(%s",
ret, baseRegexp );
/* add separator char */
if( iterDH->separator ) {
smiAsprintf( &ret, "%s%s", ret, iterDH->separator );
}
smiAsprintf( &ret, "(%s){%u,%u})%s",
ret, lengths[ i ] - 1, lengths[ i+1 ] - 1,
baseRegexp );
/* adjust the number of used octets */
octetsUsed += iterDH->number;
if( octetsUsed >= lengths[ i + 1 ] ) {
/* maximum number of octets used,
we must exit the loop */
break;
}
}
}
}
else {
/* might be the last one */
if( iterDH->type == 'd' ) {
/* decimal number needs to be treated differently */
if( iterDH->number < lengths[ i+1 ] ) {
/* we are not using the maximun number of octets */
smiAsprintf( &ret, "%s(0|[1-9]([0-9]{0,%d}))",
ret,
numDigits( smiPow( 255, iterDH->number ) ) );
/* adjust the number of used octets */
octetsUsed += lengths[ i ];
if( octetsUsed >= lengths[ i + 1 ] ) {
/* the maximum number of octets have been reached,
we must exit the loop */
break;
}
/* add separator char */
if( iterDH->separator ) {
smiAsprintf( &ret, "%s%s", ret, iterDH->separator );
}
}
else {
/* we have used the maximum number of octets */
smiAsprintf( &ret, "%s(0|[1-9]([0-9]{0,%d})",
ret,
numDigits( smiPow( 255, lengths[ i+1 ] ) ) );
}
}
else {
/* type is not decimal */
smiAsprintf( &ret, "%s(%s", ret, baseRegexp );
if( iterDH->next ) {
/* there will be another display hint block */
if( iterDH->separator ) {
smiAsprintf( &ret, "%s%s", ret, iterDH->separator );
}
if( ! lengths[ i ] && lengths[ i+1 ] == 65535 ) {
/* minLength = 0, maxLength = 65535,
* i.e. no restriction at all */
smiAsprintf( &ret, "%s)*",ret );
}
else{
/* we have a different length restriction */
smiAsprintf( &ret, "%s){%u,%u}",ret, lengths[ i ],
MIN( iterDH->number,
lengths[ i + 1] ) - 1 );
}
/* adjust the number of used octets */
octetsUsed += lengths[ i ];
if( octetsUsed >= lengths[ i + 1 ] ) {
/* the maximum number of octets have been reached,
we must exit the loop */
break;
}
}
else {
/* we are the ast display hint block */
octetsUsed += lengths[ i ];
if( iterDH->separator &&
octetsUsed < lengths[ i + 1 ] ) {
/* we have a separator char and
* still not reached the maximum number of octets */
if( ! lengths[ i ] && lengths[ i+1 ] == 65535 ) {
/* we have no length restriction */
smiAsprintf( &ret, "%s%s)*%s",
ret, iterDH->separator, baseRegexp );
}
else {
/* we have a length restriction */
smiAsprintf( &ret, "%s%s){%u,%u}%s",
ret, iterDH->separator,
lengths[ i ], lengths[ i + 1] - 1,
baseRegexp );
}
}
else {
/* we don't have a separator char or
* have used the maximum number of octets */
if( ! lengths[ i ] && lengths[ i+1 ] == 65535 ) {
/* no lengths restriction */
smiAsprintf( &ret, "%s)*%s",
ret, iterDH->separator );
/* TBD: what, if there is no separator ??? */
}
else {
/* we have a length restriction */
smiAsprintf( &ret, "%s){%u,%u}%s",
ret, lengths[ i ],
lengths[ i + 1],
iterDH->separator );
/* TBD: what, if there is no separator ??? */
}
}
}
}
if( octetsUsed >= lengths[ i + 1 ] ) {
/* the maximum number of octets have been reached,
we must exit the loop */
break;
}
}
}
/* adjust the "pointer" for the lenghts array */
i += 2;
if( i < numSubranges * 2 ) {
/* we are not the last subrange, so we have to extend the pattern */
smiAsprintf( &ret, "%s)|(", ret );
}
else {
/* we are the last subrange */
smiAsprintf( &ret, "%s)", ret );
if( ! lengths[ 0 ] ) {
smiAsprintf( &ret, "%s){0,1}", ret );
}
}
} while( i < numSubranges * 2 );
/* check if all brackets have been closed */
if( getBracketLevel( ret ) ) {
bl = getBracketLevel( ret );
fprintf( stderr, "%d\n", bl );
if( bl > 0 ) {
/* TODO: add a warning that brackets have been added */
for(; bl; bl--) {
smiAsprintf( &ret, "%s)", ret );
}
}
else {
/* TODO: some error handling */
}
}
return ret;
}
/*
* Perhaps we should introduce another parameter (flags) which
* controls whether shortcuts are taken and such things...
*/
extern char*
smiFormatToPattern(const char *format, SmiRange *smiRange)
{
SmiRange *range = smiRange;
SmiUnsigned32 *lengths = NULL;
int num = 0, lp;
char *pattern;
for (range = smiRange; range; range = smiGetNextRange(range)) {
num++;
}
/* copy ranges to array (no clue why this is being done) */
if (num) {
lengths = xmalloc(2 * num * sizeof(SmiUnsigned32));
for (range = smiRange, lp = 0; range; range = smiGetNextRange(range)) {
lengths[lp++] = range->minValue.value.unsigned32;
lengths[lp++] = range->maxValue.value.unsigned32;
}
} else {
lengths = xmalloc( 2 * sizeof(SmiUnsigned32));
lengths[0] = 0;
lengths[1] = 65535;
}
pattern = getStrDHType(format, lengths, num);
xfree(lengths);
return pattern;
}