API extended to support fractions of seconds

diff --git a/skeletons/GeneralizedTime.c b/skeletons/GeneralizedTime.c
index 770a304..45a082b 100644
--- a/skeletons/GeneralizedTime.c
+++ b/skeletons/GeneralizedTime.c
@@ -108,7 +108,7 @@
 #endif	/* _EMULATE_TIMEGM */
 
 
-#ifndef	__NO_ASN_TABLE__
+#ifndef	__ASN_INTERNAL_TEST_MODE__
 
 /*
  * GeneralizedTime basic type description.
@@ -139,7 +139,7 @@
 	0	/* No specifics */
 };
 
-#endif	/* __NO_ASN_TABLE__ */
+#endif	/* __ASN_INTERNAL_TEST_MODE__ */
 
 /*
  * Check that the time looks like the time.
@@ -163,76 +163,70 @@
 }
 
 asn_enc_rval_t
-GeneralizedTime_encode_der(asn_TYPE_descriptor_t *td, void *ptr,
+GeneralizedTime_encode_der(asn_TYPE_descriptor_t *td, void *sptr,
 	int tag_mode, ber_tlv_tag_t tag,
 	asn_app_consume_bytes_f *cb, void *app_key) {
-	GeneralizedTime_t *st = (GeneralizedTime_t *)ptr;
+	GeneralizedTime_t *st = (GeneralizedTime_t *)sptr;
 	asn_enc_rval_t erval;
+	long fv, fb;	/* seconds fraction value and base */
+	struct tm tm;
+	time_t tloc;
 
-	/* If not canonical DER, re-encode into canonical DER. */
-	if(st->size && st->buf[st->size-1] != 0x5a) {
-		struct tm tm;
-		time_t tloc;
+	/*
+	 * Encode as a canonical DER.
+	 */
+	errno = EPERM;
+	tloc = asn_GT2time_frac(st, &fv, &fb, &tm, 1);	/* Recognize time */
+	if(tloc == -1 && errno != EPERM)
+		/* Failed to recognize time. Fail completely. */
+		_ASN_ENCODE_FAILED;
 
-		errno = EPERM;
-		tloc = asn_GT2time(st, &tm, 1);	/* Recognize time */
-		if(tloc == -1 && errno != EPERM) {
-			/* Failed to recognize time. Fail completely. */
-			erval.encoded = -1;
-			erval.failed_type = td;
-			erval.structure_ptr = ptr;
-			return erval;
-		}
-		st = asn_time2GT(0, &tm, 1);	/* Save time canonically */
-		if(!st) {
-			/* Memory allocation failure. */
-			erval.encoded = -1;
-			erval.failed_type = td;
-			erval.structure_ptr = ptr;
-			return erval;
-		}
-	}
+	st = asn_time2GT_frac(0, &tm, fv, fb, 1); /* Save time canonically */
+	if(!st) _ASN_ENCODE_FAILED;	/* Memory allocation failure. */
 
 	erval = OCTET_STRING_encode_der(td, st, tag_mode, tag, cb, app_key);
 
-	if(st != ptr) {
-		FREEMEM(st->buf);
-		FREEMEM(st);
-	}
+	FREEMEM(st->buf);
+	FREEMEM(st);
 
 	return erval;
 }
 
+#ifndef	__ASN_INTERNAL_TEST_MODE__
+
 asn_enc_rval_t
 GeneralizedTime_encode_xer(asn_TYPE_descriptor_t *td, void *sptr,
 	int ilevel, enum xer_encoder_flags_e flags,
 		asn_app_consume_bytes_f *cb, void *app_key) {
-	OCTET_STRING_t st;
 
 	if(flags & XER_F_CANONICAL) {
-		char buf[32];
+		GeneralizedTime_t *gt;
+		asn_enc_rval_t rv;
+		long fv, fb;		/* fractional parts */
 		struct tm tm;
-		ssize_t ret;
 
 		errno = EPERM;
-		if(asn_GT2time((GeneralizedTime_t *)sptr, &tm, 1) == -1
+		if(asn_GT2time_frac((GeneralizedTime_t *)sptr,
+					&fv, &fb, &tm, 1) == -1
 				&& errno != EPERM)
 			_ASN_ENCODE_FAILED;
-	
-		ret = snprintf(buf, sizeof(buf), "%04d%02d%02d%02d%02d%02dZ",
-				tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
-				tm.tm_hour, tm.tm_min, tm.tm_sec);
-		assert(ret > 0 && ret < (int)sizeof(buf));
-	
-		st.buf = (uint8_t *)buf;
-		st.size = ret;
-		sptr = &st;
-	}
 
-	return OCTET_STRING_encode_xer_utf8(td, sptr, ilevel, flags,
-		cb, app_key);
+		gt = asn_time2GT_frac(0, &tm, fv, fb, 1);
+		if(!gt) _ASN_ENCODE_FAILED;
+	
+		rv = OCTET_STRING_encode_xer_utf8(td, sptr, ilevel, flags,
+			cb, app_key);
+		asn_DEF_GeneralizedTime.free_struct(&asn_DEF_GeneralizedTime,
+			gt, 0);
+		return rv;
+	} else {
+		return OCTET_STRING_encode_xer_utf8(td, sptr, ilevel, flags,
+			cb, app_key);
+	}
 }
 
+#endif	/* __ASN_INTERNAL_TEST_MODE__ */
+
 int
 GeneralizedTime_print(asn_TYPE_descriptor_t *td, const void *sptr, int ilevel,
 	asn_app_consume_bytes_f *cb, void *app_key) {
@@ -251,7 +245,7 @@
 			return (cb("<bad-value>", 11, app_key) < 0) ? -1 : 0;
 
 		ret = snprintf(buf, sizeof(buf),
-			"%04d-%02d-%02d %02d:%02d%02d (GMT)",
+			"%04d-%02d-%02d %02d:%02d:%02d (GMT)",
 			tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
 			tm.tm_hour, tm.tm_min, tm.tm_sec);
 		assert(ret > 0 && ret < (int)sizeof(buf));
@@ -263,6 +257,11 @@
 
 time_t
 asn_GT2time(const GeneralizedTime_t *st, struct tm *ret_tm, int as_gmt) {
+	return asn_GT2time_frac(st, 0, 0, ret_tm, as_gmt);
+}
+
+time_t
+asn_GT2time_frac(const GeneralizedTime_t *st, long *frac_value, long *frac_base, struct tm *ret_tm, int as_gmt) {
 	struct tm tm_s;
 	uint8_t *buf;
 	uint8_t *end;
@@ -270,6 +269,8 @@
 	int gmtoff_m = 0;
 	int gmtoff = 0;	/* h + m */
 	int offset_specified = 0;
+	long fvalue = 0;
+	long fbase = 1;
 	time_t tloc;
 
 	if(!st || !st->buf) {
@@ -365,13 +366,21 @@
 	 *               ^ ^
 	 */
 	switch(*buf) {
-	case 0x2C: case 0x2E:	/* (.|,) */
-		/* Fractions of seconds are not supported
-		 * by time_t or struct tm. Skip them */
+	case 0x2C: case 0x2E: /* (.|,) */
+		/*
+		 * Process fractions of seconds.
+		 */
 		for(buf++; buf < end; buf++) {
-			switch(*buf) {
+			int v = *buf;
+			switch(v) {
 			case 0x30: case 0x31: case 0x32: case 0x33: case 0x34:
 			case 0x35: case 0x36: case 0x37: case 0x38: case 0x39:
+				if((fbase * 10 / fbase) != 10) {
+					/* Not enough precision, ignore */
+				} else {
+					fbase *= 10;
+					fvalue = fvalue * 10 + (v - 0x30);
+				}
 				continue;
 			default:
 				break;
@@ -479,15 +488,29 @@
 		}
 	}
 
+	/* Fractions of seconds */
+	if(frac_value) *frac_value = fvalue;
+	if(frac_base) *frac_base = fbase;
+
 	return tloc;
 }
 
-
 GeneralizedTime_t *
 asn_time2GT(GeneralizedTime_t *opt_gt, const struct tm *tm, int force_gmt) {
+	return asn_time2GT_frac(opt_gt, tm, 0, 0, force_gmt);
+}
+
+GeneralizedTime_t *
+asn_time2GT_frac(GeneralizedTime_t *opt_gt, const struct tm *tm, long frac_value, long frac_base, int force_gmt) {
 	struct tm tm_s;
 	long gmtoff;
-	const unsigned int buf_size = 24; /* 4+2+2 +2+2+2 +4 + cushion */
+	const unsigned int buf_size =
+		4 + 2 + 2	/* yyyymmdd */
+		+ 2 + 2 + 2	/* hhmmss */
+		+ 1 + 6		/* .ffffff */
+		+ 1 + 4		/* +hhmm */
+		+ 1		/* '\0' */
+		;
 	char *buf;
 	char *p;
 	int size;
@@ -524,17 +547,48 @@
 		tm->tm_min,
 		tm->tm_sec
 	);
-	assert(size == 14);
+	if(size != 14) {
+		/* Could be assert(size == 14); */
+		FREEMEM(buf);
+		errno = EINVAL;
+		return 0;
+	}
 
 	p = buf + size;
+
+	/*
+	 * Deal with fractions.
+	 */
+	if(frac_base >= 10 && frac_value > 0) {
+		char *end = p + 1 + 6;	/* '.' + maximum 6 digits */
+		char *z;
+		*p++ = '.';
+		do {
+			int digit;
+			frac_base /= 10;
+			digit = frac_value / frac_base;
+			frac_value %= frac_base;
+			*p++ = digit + 0x30;
+		} while(frac_base >= 10 && frac_value > 0 && p < end);
+		for(z = p - 1; *z == 0x30; --z);	/* Strip zeroes */
+		p = z + (*z != '.');
+		size = p - buf;
+	}
+
 	if(force_gmt) {
 		*p++ = 0x5a;	/* "Z" */
 		*p++ = 0;
 		size++;
 	} else {
-		int ret = snprintf(p, buf_size - size, "%+03ld%02ld",
-			gmtoff / 3600, gmtoff % 3600);
-		assert(ret >= 5 && ret <= 7);
+		int ret;
+		gmtoff %= 86400;
+		ret = snprintf(p, buf_size - size, "%+03ld%02ld",
+			gmtoff / 3600, labs(gmtoff % 3600));
+		if(ret != 5) {
+			FREEMEM(buf);
+			errno = EINVAL;
+			return 0;
+		}
 		size += ret;
 	}
 
diff --git a/skeletons/GeneralizedTime.h b/skeletons/GeneralizedTime.h
index 21a680c..90f7e08 100644
--- a/skeletons/GeneralizedTime.h
+++ b/skeletons/GeneralizedTime.h
@@ -32,14 +32,21 @@
 time_t asn_GT2time(const GeneralizedTime_t *, struct tm *_optional_tm4fill,
 	int as_gmt);
 
+/* A version of the above function also returning the fractions of seconds */
+time_t asn_GT2time_frac(const GeneralizedTime_t *,
+	long *frac_value, long *frac_base,		/* (value/base) */
+	struct tm *_optional_tm4fill, int as_gmt);
+
 /*
  * Convert a struct tm into GeneralizedTime.
- * If __opt_gt is not given, this function will try to allocate one.
+ * If _optional_gt is not given, this function will try to allocate one.
  * If force_gmt is given, the resulting GeneralizedTime will be forced
  * into a GMT time zone (encoding ends with a "Z").
  * On error, this function returns 0 and sets errno.
  */
-GeneralizedTime_t *asn_time2GT(GeneralizedTime_t *__opt_gt, const struct tm *,
-	int force_gmt);
+GeneralizedTime_t *asn_time2GT(GeneralizedTime_t *_optional_gt,
+	const struct tm *, int force_gmt);
+GeneralizedTime_t *asn_time2GT_frac(GeneralizedTime_t *_optional_gt,
+	const struct tm *, long frac_value, long frac_base, int force_gmt);
 
 #endif	/* _GeneralizedTime_H_ */
diff --git a/skeletons/tests/check-GeneralizedTime.c b/skeletons/tests/check-GeneralizedTime.c
index ff20772..abd44b0 100644
--- a/skeletons/tests/check-GeneralizedTime.c
+++ b/skeletons/tests/check-GeneralizedTime.c
@@ -1,31 +1,34 @@
-#define	__NO_ASN_TABLE__
+#define	__ASN_INTERNAL_TEST_MODE__
 #include <GeneralizedTime.c>
 #include <constraints.c>
 
 static void
-check(char *time_str, time_t expect, int as_gmt) {
+recognize(char *time_str, time_t expect, int as_gmt) {
 	GeneralizedTime_t gt;
 	struct tm tm;
 	time_t tloc;
+	long fv, fb;
 
-	gt.buf = time_str;
+	gt.buf = (uint8_t *)time_str;
 	gt.size = strlen(time_str);
 
-	tloc = asn_GT2time(&gt, &tm, as_gmt);
+	tloc = asn_GT2time_frac(&gt, &fv, &fb, &tm, as_gmt);
 	printf("%s: [%s] -> %ld == %ld\n",
 		as_gmt?"GMT":"ofs", time_str, (long)tloc, (long)expect);
 
 	if(tloc != -1) {
-		printf("\t%04d-%02d-%02dT%02d:%02d:%02d%+03ld%02ld\n",
+		printf("\t%04d-%02d-%02dT%02d:%02d:%02d(.%ld/%ld)%+03ld%02ld\n",
 		tm.tm_year + 1900,
 		tm.tm_mon + 1,
 		tm.tm_mday,
 		tm.tm_hour,
 		tm.tm_min,
 		tm.tm_sec,
+		fv, fb,
 		(GMTOFF(tm) / 3600),
 		labs(GMTOFF(tm) % 3600)
 		);
+		assert(fb < 100 || (fb % 100) == 0);
 	}
 	assert(tloc == expect);
 
@@ -33,11 +36,11 @@
 	assert(tloc == -1 || as_gmt == 0 || GMTOFF(tm) == 0);
 #endif
 
-	if(!as_gmt) check(time_str, expect, 1);
+	if(!as_gmt) recognize(time_str, expect, 1);
 }
 
 static void
-rcheck(time_t tloc, const char *expect, int force_gmt) {
+encode(time_t tloc, const char *expect, int force_gmt) {
 	GeneralizedTime_t *gt;
 	struct tm tm, *tmp;
 
@@ -49,51 +52,97 @@
 		assert(expect);
 		printf("[%s] vs [%s] (%d)\n",
 			gt->buf, expect, force_gmt);
-		assert(gt->size == (int)strlen(gt->buf));
-		assert(!strcmp(gt->buf, expect));
+		assert(gt->size == (int)strlen((char *)gt->buf));
+		assert(!strcmp((char *)gt->buf, expect));
 	} else {
 		assert(!expect);
 	}
 }
 
+
+static void
+recode(char *time_str, const char *expect) {
+	long frac_value, frac_base;
+	GeneralizedTime_t gt;
+	struct tm tm;
+	time_t tloc;
+
+	gt.buf = (uint8_t *)time_str;
+	gt.size = strlen(time_str);
+
+	tloc = asn_GT2time_frac(&gt, &frac_value, &frac_base, &tm, 1);
+	assert(tloc != -1);
+
+	gt.buf = 0;
+	asn_time2GT_frac(&gt, &tm, frac_value, frac_base, 1);
+	assert(gt.buf);
+
+	printf("[%s] => [%s] == [%s]\n", time_str, gt.buf, expect);
+
+	assert(strcmp((char *)gt.buf, expect) == 0);
+	FREEMEM(gt.buf);
+}
+
 int
 main(int ac, char **av) {
 
 	(void)av;
 
-	check("200401250", -1, 0);
-	check("2004012509300", -1, 0);
-	check("20040125093000-", -1, 0);
-	check("20040125093007-0", -1, 0);
-	check("20040125093007-080", -1, 0);
-	check("200401250930.01Z", -1, 0);
+	recognize("200401250", -1, 0);
+	recognize("2004012509300", -1, 0);
+	recognize("20040125093000-", -1, 0);
+	recognize("20040125093007-0", -1, 0);
+	recognize("20040125093007-080", -1, 0);
+	recognize("200401250930.01Z", -1, 0);
 
 	/* These six are from X.690:11.7.5 */
-	check("19920520240000Z", -1, 0);  /* midnight represented incorrectly */
-	//check("19920622123421.0Z", -1, 0);  /* spurious trailing zeros */
-	//check("19920722132100.30Z", -1, 0); /* spurious trailing zeros */
-	check("19920521000000Z", 706406400, 0);
-	check("19920622123421Z", 709216461, 0);
-	check("19920722132100.3Z", 711811260, 0);
+	recognize("19920520240000Z", -1, 0);  /* midnight represented incorrectly */
+	recognize("19920622123421.0Z", 709216461, 0);  /* spurious trailing zeros */
+	recognize("19920722132100.30Z", 711811260, 0); /* spurious trailing zeros */
+	recognize("19920521000000Z", 706406400, 0);
+	recognize("19920622123421Z", 709216461, 0);
+	recognize("19920722132100.3Z", 711811260, 0);
 
-	check("20040125093007Z", 1075023007, 0);
-	check("20040125093007+00", 1075023007, 0);
-	check("20040125093007.01+0000", 1075023007, 0);
-	check("20040125093007,1+0000", 1075023007, 0);
-	check("20040125093007-0800", 1075051807, 0);
+	recognize("20040125093007Z", 1075023007, 0);
+	recognize("20040125093007+00", 1075023007, 0);
+	recognize("20040125093007.01+0000", 1075023007, 0);
+	recognize("20040125093007,1+0000", 1075023007, 0);
+	recognize("20040125093007-0800", 1075051807, 0);
 
-	rcheck(1075023007, "20040125093007Z", 1);
+	recognize("19920722132100.123000123Z", 711811260, 0);
+	recognize("19920722132100.1230000123Z", 711811260, 0);
+	recognize("19920722132100.12300000123Z", 711811260, 0);
+
+	encode(1075023007, "20040125093007Z", 1);
 
 	if(ac > 1) {
 		/* These will be valid only inside PST time zone */
-		check("20040125093007", 1075051807, 0);
-		check("200401250930", 1075051800, 0);
-		check("20040125093000,01", 1075051800, 0);
-		check("20040125093000,1234", 1075051800, 0);
+		recognize("20040125093007", 1075051807, 0);
+		recognize("200401250930", 1075051800, 0);
+		recognize("20040125093000,01", 1075051800, 0);
+		recognize("20040125093000,1234", 1075051800, 0);
 
-		rcheck(1075023007, "20040125013007-0800", 0);
+		encode(1075023007, "20040125013007-0800", 0);
+		recode("20050702123312", "20050702193312Z");
 	}
 
+	recode("20050702123312Z", "20050702123312Z");
+	recode("20050702123312+01", "20050702113312Z");
+	recode("20050702123312,0+01", "20050702113312Z");
+	recode("20050702123312,1+01", "20050702113312.1Z");
+	recode("20050702123312.01+01", "20050702113312.01Z");
+	recode("20050702123312.00+01", "20050702113312Z");
+	recode("20050702123312.30+01", "20050702113312.3Z");
+	recode("20050702123312,30000+01", "20050702113312.3Z");
+	recode("20050702123312,300000000+01", "20050702113312.3Z");
+	recode("20050702123312.123456+01", "20050702113312.123456Z");
+	recode("20050702123312.1234567+01", "20050702113312.123456Z");
+	recode("20050702123312.12345678+01", "20050702113312.123456Z");
+	recode("20050702123312.123456789+01", "20050702113312.123456Z");
+	recode("20050702123312.000001+01", "20050702113312.000001Z");
+	recode("20050702123312.0000001Z", "20050702123312Z");
+	recode("20050702123312.0080010+1056", "20050702013712.008001Z");
+
 	return 0;
 }
 
diff --git a/skeletons/tests/check-UTCTime.c b/skeletons/tests/check-UTCTime.c
index 4c76858..96803ad 100644
--- a/skeletons/tests/check-UTCTime.c
+++ b/skeletons/tests/check-UTCTime.c
@@ -1,6 +1,5 @@
-#define	__NO_ASN_TABLE__
+#define	__ASN_INTERNAL_TEST_MODE__
 #include <GeneralizedTime.c>
-#define	__NO_ASN_TABLE__
 #include <UTCTime.c>
 #include <constraints.c>
 
@@ -10,7 +9,7 @@
 	struct tm tm;
 	time_t tloc;
 
-	gt.buf = time_str;
+	gt.buf = (uint8_t *)time_str;
 	gt.size = strlen(time_str);
 
 	tloc = asn_UT2time(&gt, &tm, as_gmt);
diff --git a/skeletons/tests/check-UTF8String.c b/skeletons/tests/check-UTF8String.c
index ae368ea..10592a1 100644
--- a/skeletons/tests/check-UTF8String.c
+++ b/skeletons/tests/check-UTF8String.c
@@ -43,7 +43,7 @@
 	int ret;
 	int i;
 
-	st.buf = long_test;
+	st.buf = (uint8_t *)long_test;
 	st.size = sizeof(long_test) - 1;
 
 	ret = UTF8String_length(&st);