improved asn_GT2time() and added asn_time2GT() function

diff --git a/ChangeLog b/ChangeLog
index d1371bf..6af592e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,7 @@
 
 	* Fixed application-level problem in SET OF/SEQUENCE OF array cleanup.
 	  (Severity: medium, Security impact: low)
+	* Improved asn_GT2time() and added asn_time2{GT,UT}() functions.
 
 0.8.15:	2004-Jul-20
 
diff --git a/skeletons/GeneralizedTime.c b/skeletons/GeneralizedTime.c
index 1dadb92..5a3120d 100644
--- a/skeletons/GeneralizedTime.c
+++ b/skeletons/GeneralizedTime.c
@@ -21,7 +21,7 @@
 	"GeneralizedTime",
 	GeneralizedTime_constraint, /* Check validity of time */
 	OCTET_STRING_decode_ber,    /* Implemented in terms of OCTET STRING */
-	OCTET_STRING_encode_der,    /* Implemented in terms of OCTET STRING */
+	GeneralizedTime_encode_der, /* Implemented in terms of OCTET STRING */
 	GeneralizedTime_print,
 	OCTET_STRING_free,
 	0, /* Use generic outmost tag fetcher */
@@ -45,7 +45,7 @@
 	time_t tloc;
 
 	errno = EPERM;			/* Just an unlikely error code */
-	tloc = asn_GT2time(st, 0);
+	tloc = asn_GT2time(st, 0, 0);
 	if(tloc == -1 && errno != EPERM) {
 		_ASN_ERRLOG("%s: Invalid time format: %s",
 			td->name, strerror(errno));
@@ -55,6 +55,47 @@
 	return 0;
 }
 
+der_enc_rval_t
+GeneralizedTime_encode_der(asn1_TYPE_descriptor_t *td, void *ptr,
+	int tag_mode, ber_tlv_tag_t tag,
+	asn_app_consume_bytes_f *cb, void *app_key) {
+	GeneralizedTime_t *st = ptr;
+	der_enc_rval_t erval;
+
+	/* If not canonical DER, re-encode into canonical DER. */
+	if(st->size && st->buf[st->size-1] != 'Z') {
+		struct tm tm;
+		time_t tloc;
+
+		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;
+		}
+	}
+
+	erval = OCTET_STRING_encode_der(td, st, tag_mode, tag, cb, app_key);
+
+	if(st != ptr) {
+		FREEMEM(st->buf);
+		FREEMEM(st);
+	}
+
+	return erval;
+}
+
 int
 GeneralizedTime_print(asn1_TYPE_descriptor_t *td, const void *sptr, int ilevel,
 	asn_app_consume_bytes_f *cb, void *app_key) {
@@ -69,11 +110,11 @@
 		int ret;
 
 		errno = EPERM;
-		if(asn_GT2time(st, &tm) == -1 && errno != EPERM)
+		if(asn_GT2time(st, &tm, 1) == -1 && errno != EPERM)
 			return cb("<bad-value>", 11, app_key);
 
 		ret = snprintf(buf, sizeof(buf),
-			"%04d-%02d-%02d %02d:%02d%02d",
+			"%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));
@@ -96,19 +137,19 @@
  * Where to look for offset from GMT, Phase II.
  */
 #ifdef	HAVE_TM_ZONE
-#define	GMTOFF	(tm_s.tm_gmtoff)
+#define	GMTOFF(tm)	((tm).tm_gmtoff)
 #else	/* HAVE_TM_ZONE */
-#define	GMTOFF	(-timezone)
+#define	GMTOFF(tm)	(-timezone)
 #endif	/* HAVE_TM_ZONE */
 
 time_t
-asn_GT2time(const GeneralizedTime_t *st, struct tm *_tm) {
+asn_GT2time(const GeneralizedTime_t *st, struct tm *ret_tm, int as_gmt) {
 	struct tm tm_s;
 	uint8_t *buf;
 	uint8_t *end;
-	int tm_gmtoff_h = 0;
-	int tm_gmtoff_m = 0;
-	int tm_gmtoff = 0;	/* h + m */
+	int gmtoff_h = 0;
+	int gmtoff_m = 0;
+	int gmtoff = 0;	/* h + m */
 	int offset_specified = 0;
 	time_t tloc;
 
@@ -240,22 +281,22 @@
 		return -1;
 	}
 	buf++;
-	B2F(tm_gmtoff_h);
-	B2F(tm_gmtoff_h);
+	B2F(gmtoff_h);
+	B2F(gmtoff_h);
 	if(buf[-3] == 0x2D)	/* Negative */
-		tm_gmtoff = -1;
+		gmtoff = -1;
 	else
-		tm_gmtoff = 1;
+		gmtoff = 1;
 
 	if((end - buf) == 2) {
-		B2F(tm_gmtoff_m);
-		B2F(tm_gmtoff_m);
+		B2F(gmtoff_m);
+		B2F(gmtoff_m);
 	} else if(end != buf) {
 		errno = EINVAL;
 		return -1;
 	}
 
-	tm_gmtoff = tm_gmtoff * (3600 * tm_gmtoff_h + 60 * tm_gmtoff_m);
+	gmtoff = gmtoff * (3600 * gmtoff_h + 60 * gmtoff_m);
 
 	/* Fall through */
 utc_finish:
@@ -282,25 +323,110 @@
 	tm_s.tm_year -= 1900;
 	tm_s.tm_isdst = -1;
 
-	tloc = mktime(&tm_s);
+	tm_s.tm_sec -= gmtoff;
+
+	/*** AT THIS POINT tm_s is either GMT or local (unknown) ****/
+
+	if(offset_specified)
+		tloc = timegm(&tm_s);
+	else {
+		/*
+		 * Without an offset (or 'Z'),
+		 * we can only guess that it is a local zone.
+		 * Interpret it in this fashion.
+		 */
+		tloc = mktime(&tm_s);
+	}
 	if(tloc == -1) {
 		errno = EINVAL;
 		return -1;
 	}
 
-	if(offset_specified) {
-		/*
-		 * Offset from GMT is specified in the time expression.
-		 */
-		tloc += GMTOFF - tm_gmtoff;
-		if(_tm && (localtime_r(&tloc, &tm_s) == NULL)) {
-			/* Could not reconstruct the time */
-			return -1;
+	if(ret_tm) {
+		if(as_gmt) {
+			if(offset_specified) {
+				*ret_tm = tm_s;
+			} else {
+				if(gmtime_r(&tloc, ret_tm) == 0) {
+					errno = EINVAL;
+					return -1;
+				}
+			}
+		} else {
+			if(localtime_r(&tloc, ret_tm) == 0) {
+				errno = EINVAL;
+				return -1;
+			}
 		}
 	}
 
-	if(_tm) memcpy(_tm, &tm_s, sizeof(struct tm));
-
 	return tloc;
 }
 
+
+GeneralizedTime_t *
+asn_time2GT(GeneralizedTime_t *opt_gt, const struct tm *tm, int force_gmt) {
+	struct tm tm_s;
+	long gmtoff;
+	const unsigned int buf_size = 24; /* 4+2+2 +2+2+2 +4 + cushion */
+	char *buf;
+	char *p;
+	int size;
+
+	/* Check arguments */
+	if(!tm) {
+		errno = EINVAL;
+		return 0;
+	}
+
+	/* Pre-allocate a buffer of sufficient yet small length */
+	buf = MALLOC(buf_size);
+	if(!buf) return 0;
+
+	gmtoff = GMTOFF(*tm);
+
+	if(force_gmt && gmtoff) {
+		tm_s = *tm;
+		tm_s.tm_sec -= gmtoff;
+		timegm(&tm_s);	/* Fix the time */
+		assert(!GMTOFF(tm_s));
+		tm = &tm_s;
+	}
+
+	size = snprintf(buf, buf_size, "%04d%02d%02d%02d%02d%02d",
+		tm->tm_year + 1900,
+		tm->tm_mon + 1,
+		tm->tm_mday,
+		tm->tm_hour,
+		tm->tm_min,
+		tm->tm_sec
+	);
+	assert(size == 14);
+
+	p = buf + size;
+	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);
+		size += ret;
+	}
+
+	if(opt_gt) {
+		if(opt_gt->buf)
+			FREEMEM(opt_gt->buf);
+	} else {
+		opt_gt = CALLOC(1, sizeof *opt_gt);
+		if(!opt_gt) { free(buf); return 0; }
+	}
+
+	opt_gt->buf = buf;
+	opt_gt->size = size;
+
+	return opt_gt;
+}
+
+
diff --git a/skeletons/GeneralizedTime.h b/skeletons/GeneralizedTime.h
index d8176be..a7a8a4a 100644
--- a/skeletons/GeneralizedTime.h
+++ b/skeletons/GeneralizedTime.h
@@ -13,14 +13,33 @@
 extern asn1_TYPE_descriptor_t asn1_DEF_GeneralizedTime;
 
 asn_constr_check_f GeneralizedTime_constraint;
+der_type_encoder_f GeneralizedTime_encode_der;
 asn_struct_print_f GeneralizedTime_print;
 
 /***********************
  * Some handy helpers. *
  ***********************/
 
-/* On error returns -1 and errno set to EINVAL */
 struct tm;	/* <time.h> */
-time_t asn_GT2time(const GeneralizedTime_t *, struct tm *_optional_tm4fill);
+
+/*
+ * Convert a GeneralizedTime structure into time_t
+ * and optionally into struct tm.
+ * If as_gmt is given, the resulting _optional_tm4fill will have a GMT zone,
+ * instead of default local one.
+ * On error returns -1 and errno set to EINVAL
+ */
+time_t asn_GT2time(const GeneralizedTime_t *, 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 force_gmt is given, the resulting GeneralizedTime will be forced
+ * into a GMT time zone (encoding ends with 'Z').
+ * On error, this function returns 0 and sets errno.
+ */
+GeneralizedTime_t *asn_time2GT(GeneralizedTime_t *__opt_gt, const struct tm *,
+	int force_gmt);
 
 #endif	/* _GeneralizedTime_H_ */
diff --git a/skeletons/tests/check-GeneralizedTime.c b/skeletons/tests/check-GeneralizedTime.c
index 3451cff..5a6cb0b 100644
--- a/skeletons/tests/check-GeneralizedTime.c
+++ b/skeletons/tests/check-GeneralizedTime.c
@@ -3,7 +3,7 @@
 #include "../constraints.c"
 
 static void
-check(char *time_str, time_t sample) {
+check(char *time_str, time_t expect, int as_gmt) {
 	GeneralizedTime_t gt;
 	struct tm tm;
 	time_t tloc;
@@ -11,44 +11,91 @@
 	gt.buf = time_str;
 	gt.size = strlen(time_str);
 
-	tloc = asn_GT2time(&gt, &tm);
-	printf("[%s] -> %ld == %ld\n", time_str, (long)tloc, (long)sample);
+	tloc = asn_GT2time(&gt, &tm, as_gmt);
+	printf("%s: [%s] -> %ld == %ld\n",
+		as_gmt?"GMT":"ofs", time_str, (long)tloc, (long)expect);
 	if(tloc != -1)
-	printf("\t%d-%d-%dT%02d:%02d:%02d %ld\n",
+	printf("\t%04d-%02d-%02dT%02d:%02d:%02d%+03ld%02ld\n",
 		tm.tm_year + 1900,
 		tm.tm_mon + 1,
 		tm.tm_mday,
 		tm.tm_hour,
 		tm.tm_min,
 		tm.tm_sec,
-		tm.tm_gmtoff
+		(tm.tm_gmtoff / 3600),
+		labs(tm.tm_gmtoff % 3600)
 	);
-	assert(tloc == sample);
+	assert(tloc == expect);
+
+	assert(tloc == -1 || as_gmt == 0 || tm.tm_gmtoff == 0);
+
+	if(!as_gmt) check(time_str, expect, 1);
+}
+
+static void
+rcheck(time_t tloc, const char *expect, int force_gmt) {
+	GeneralizedTime_t *gt;
+	struct tm tm, *tmp;
+
+	tmp = localtime_r(&tloc, &tm);
+	assert(tmp);
+
+	gt = asn_time2GT(0, &tm, force_gmt);
+	if(gt) {
+		assert(expect);
+		printf("[%s] vs [%s] (%d)\n",
+			gt->buf, expect, force_gmt);
+		assert(gt->size == strlen(gt->buf));
+		assert(!strcmp(gt->buf, expect));
+	} else {
+		assert(!expect);
+	}
 }
 
 int
 main(int ac, char **av) {
 
-	check("200401250", -1);
-	check("2004012509300", -1);
-	check("20040125093000-", -1);
-	check("20040125093007-0", -1);
-	check("20040125093007-080", -1);
-	check("200401250930.01Z", -1);
+	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);
 
-	check("20040125093007Z", 1075023007);
-	check("20040125093007+00", 1075023007);
-	check("20040125093007.01+0000", 1075023007);
-	check("20040125093007,1+0000", 1075023007);
-	check("20040125093007-0800", 1075051807);
+	/* 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);
+
+	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);
 
 	if(ac > 1) {
 		/* These will be valid only inside PST time zone */
-		check("20040125093007", 1075051807);
-		check("200401250930", 1075051800);
-		check("20040125093000,01", 1075051800);
-		check("20040125093000,1234", 1075051800);
+		check("20040125093007", 1075051807, 0);
+		check("200401250930", 1075051800, 0);
+		check("20040125093000,01", 1075051800, 0);
+		check("20040125093000,1234", 1075051800, 0);
 	}
 
+	rcheck(1075023007, "20040125013007-0800", 0);
+	rcheck(1075023007, "20040125093007Z", 1);
+
 	return 0;
 }
+
+/*
+ * Dummy function.
+ */
+
+der_enc_rval_t
+OCTET_STRING_encode_der(asn1_TYPE_descriptor_t *td, void *ptr, int tag_mode, ber_tlv_tag_t tag, asn_app_consume_bytes_f *cb, void *app_key) {
+	der_enc_rval_t erval;
+	return erval;
+}