PER-encoding of integers wider than 32 bits
diff --git a/ChangeLog b/ChangeLog
index dff73bd..7905dc6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -8,6 +8,7 @@
     * Default constraint checking fix.
       Thanks to Bartosz Marcinkiewicz <bma@megawatt.com.pl>
     * Get rid of non-standard pointer arithmetics on void* pointer.
+    * PER-encoding of integers wider than 32 bits.
 
 0.9.24:	2013-Mar-16
 
diff --git a/asn1c/tests/check-134.-gen-PER.c b/asn1c/tests/check-134.-gen-PER.c
new file mode 100644
index 0000000..d129fe3
--- /dev/null
+++ b/asn1c/tests/check-134.-gen-PER.c
@@ -0,0 +1,115 @@
+/*
+ * Verify INTEGER values with greater than 32 bits range.
+ */
+#undef	NDEBUG
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <T.h>
+
+#ifndef  _LP64
+int main() {
+    assert(sizeof(void *) < 8);
+    return;
+}
+#else   /* 64-bit platform */
+
+static unsigned long i2ul(const INTEGER_t *i) {
+    unsigned long l;
+    int ret = asn_INTEGER2ulong(i, &l);
+    assert(ret == 0);
+    return l;
+}
+
+static void ul2i(INTEGER_t *i, unsigned long l) {
+    int ret = asn_ulong2INTEGER(i, l);
+    assert(ret == 0);
+}
+
+static void
+verify(int testNo, T_t *ti) {
+	asn_enc_rval_t er;
+	asn_dec_rval_t rv;
+	unsigned char buf[16];
+	T_t *to = 0;
+
+	fprintf(stderr, "%d IN: { %lu, %lu }\n", testNo,
+		i2ul(&ti->unsigned33), i2ul(&ti->unsigned42));
+
+	er = uper_encode_to_buffer(&asn_DEF_T, ti, buf, sizeof buf);
+	assert(er.encoded == 33 + 42);
+
+	rv = uper_decode(0, &asn_DEF_T, (void *)&to, buf, sizeof buf, 0, 0);
+	assert(rv.code == RC_OK);
+
+	fprintf(stderr, "%d ENC: %2x%2x%2x%2x %2x%2x%2x%2x\n", testNo,
+		buf[0], buf[1], buf[2], buf[3],
+		buf[4], buf[5], buf[6], buf[7]);
+	fprintf(stderr, "%d OUT: { %lu, %lu } vs { %lu, %lu }\n",
+		testNo,
+		i2ul(&ti->unsigned33), i2ul(&ti->unsigned42),
+		i2ul(&to->unsigned33), i2ul(&to->unsigned42));
+	assert(i2ul(&ti->unsigned33) == i2ul(&to->unsigned33));
+	assert(i2ul(&ti->unsigned42) == i2ul(&to->unsigned42));
+
+	xer_fprint(stderr, &asn_DEF_T, ti);
+	xer_fprint(stderr, &asn_DEF_T, to);
+}
+
+static void
+NO_encode(int testNo, T_t *ti) {
+	asn_enc_rval_t er;
+	unsigned char buf[16];
+
+	fprintf(stderr, "%d IN: { %lu, %lu }\n", testNo,
+		i2ul(&ti->unsigned33), i2ul(&ti->unsigned42));
+
+	er = uper_encode_to_buffer(&asn_DEF_T, ti, buf, sizeof buf);
+	assert(er.encoded == -1);
+}
+
+int main() {
+	T_t ti;
+
+    memset(&ti, 0, sizeof(ti));
+    ul2i(&ti.unsigned33, 0);
+    ul2i(&ti.unsigned42, 0);
+	verify(1, &ti);
+
+    ul2i(&ti.unsigned33, 1);
+    ul2i(&ti.unsigned42, 1);
+	verify(2, &ti);
+
+    ul2i(&ti.unsigned33, 5000000000);
+    ul2i(&ti.unsigned42, 3153600000000);
+	verify(3, &ti);
+
+    ul2i(&ti.unsigned33, -1);
+    ul2i(&ti.unsigned42, 0);
+	NO_encode(4, &ti);
+
+    ul2i(&ti.unsigned33, 0);
+    ul2i(&ti.unsigned42, -1);
+	NO_encode(5, &ti);
+
+    ul2i(&ti.unsigned33, 5000000000 + 1);
+    ul2i(&ti.unsigned42, 0);
+	NO_encode(6, &ti);
+
+    ul2i(&ti.unsigned33, 0);
+    ul2i(&ti.unsigned42, 3153600000000 + 1);
+	NO_encode(7, &ti);
+
+    ul2i(&ti.unsigned33, 5000000000 - 1);
+    ul2i(&ti.unsigned42, 3153600000000 - 1);
+	verify(8, &ti);
+
+	return 0;
+}
+
+#endif  /* 64-bit platform */
diff --git a/skeletons/INTEGER.c b/skeletons/INTEGER.c
index 4dfe530..9ce6960 100644
--- a/skeletons/INTEGER.c
+++ b/skeletons/INTEGER.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007 Lev Walkin <vlm@lionet.info>.
+ * Copyright (c) 2003-2014 Lev Walkin <vlm@lionet.info>.
  * All rights reserved.
  * Redistribution and modifications are permitted subject to BSD license.
  */
@@ -595,30 +595,35 @@
 		}
 	}
 
-	/* X.691, #12.2.2 */
+	/* X.691-2008/11, #13.2.2, constrained whole number */
 	if(ct && ct->flags != APC_UNCONSTRAINED) {
-		/* #10.5.6 */
+		/* #11.5.6 */
 		ASN_DEBUG("Integer with range %d bits", ct->range_bits);
 		if(ct->range_bits >= 0) {
-			long value;
-			if(ct->range_bits == 32) {
-				long lhalf;
-				value = per_get_few_bits(pd, 16);
-				if(value < 0) _ASN_DECODE_STARVED;
-				lhalf = per_get_few_bits(pd, 16);
-				if(lhalf < 0) _ASN_DECODE_STARVED;
-				value = (value << 16) | lhalf;
-			} else {
-				value = per_get_few_bits(pd, ct->range_bits);
-				if(value < 0) _ASN_DECODE_STARVED;
-			}
-			ASN_DEBUG("Got value %ld + low %ld",
-				value, ct->lower_bound);
-			value += ct->lower_bound;
-			if((specs && specs->field_unsigned)
-				? asn_ulong2INTEGER(st, value)
-				: asn_long2INTEGER(st, value))
+			if((size_t)ct->range_bits > 8 * sizeof(unsigned long))
 				_ASN_DECODE_FAILED;
+
+			if(specs && specs->field_unsigned) {
+				unsigned long uvalue;
+				if(uper_get_constrained_whole_number(pd,
+					&uvalue, ct->range_bits))
+					_ASN_DECODE_STARVED;
+				ASN_DEBUG("Got value %lu + low %ld",
+					uvalue, ct->lower_bound);
+				uvalue += ct->lower_bound;
+				if(asn_ulong2INTEGER(st, uvalue))
+					_ASN_DECODE_FAILED;
+			} else {
+				unsigned long svalue;
+				if(uper_get_constrained_whole_number(pd,
+					&svalue, ct->range_bits))
+					_ASN_DECODE_STARVED;
+				ASN_DEBUG("Got value %ld + low %ld",
+					svalue, ct->lower_bound);
+				svalue += ct->lower_bound;
+				if(asn_long2INTEGER(st, svalue))
+					_ASN_DECODE_FAILED;
+			}
 			return rval;
 		}
 	} else {
@@ -725,22 +730,14 @@
 	}
 
 
-	/* X.691, #12.2.2 */
+	/* X.691-11/2008, #13.2.2, test if constrained whole number */
 	if(ct && ct->range_bits >= 0) {
-		/* #10.5.6 */
+		/* #11.5.6 -> #11.3 */
 		ASN_DEBUG("Encoding integer with range %d bits",
 			ct->range_bits);
-		if(ct->range_bits == 32) {
-			/* TODO: extend to >32 bits */
-			long v = value - ct->lower_bound;
-			if(per_put_few_bits(po, v >> 1, 31)
-			|| per_put_few_bits(po, v, 1))
-				_ASN_ENCODE_FAILED;
-		} else {
-			if(per_put_few_bits(po, value - ct->lower_bound,
-				ct->range_bits))
-				_ASN_ENCODE_FAILED;
-		}
+		long v = value - ct->lower_bound;
+		if(uper_put_constrained_whole_number_s(po, v, ct->range_bits))
+			_ASN_ENCODE_FAILED;
 		_ASN_ENCODED_OK(er);
 	}
 
diff --git a/skeletons/per_support.c b/skeletons/per_support.c
index 2481fff..0d089f4 100644
--- a/skeletons/per_support.c
+++ b/skeletons/per_support.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2006, 2007 Lev Walkin <vlm@lionet.info>.
+ * Copyright (c) 2005-2014 Lev Walkin <vlm@lionet.info>.
  * All rights reserved.
  * Redistribution and modifications are permitted subject to BSD license.
  */
@@ -238,8 +238,8 @@
 }
 
 /*
- * Put the normally small non-negative whole number.
- * X.691, #10.6
+ * X.691-11/2008, #11.6
+ * Encoding of a normally small non-negative whole number
  */
 int
 uper_put_nsnnwn(asn_per_outp_t *po, int n) {
@@ -264,6 +264,58 @@
 }
 
 
+/* X.691-2008/11, #11.5.6 -> #11.3 */
+int uper_get_constrained_whole_number(asn_per_data_t *pd, unsigned long *out_value, int nbits) {
+	unsigned long lhalf;    /* Lower half of the number*/
+	long half;
+
+	if(nbits <= 31) {
+		half = per_get_few_bits(pd, nbits);
+		if(half < 0) return -1;
+		*out_value = half;
+		return 0;
+	}
+
+	if((size_t)nbits > 8 * sizeof(*out_value))
+		return -1;  /* RANGE */
+
+	half = per_get_few_bits(pd, 31);
+	if(half < 0) return -1;
+
+	if(uper_get_constrained_whole_number(pd, &lhalf, nbits - 31))
+		return -1;
+
+	*out_value = ((unsigned long)half << (nbits - 31)) | lhalf;
+	return 0;
+}
+
+
+/* X.691-2008/11, #11.5.6 -> #11.3 */
+int uper_put_constrained_whole_number_s(asn_per_outp_t *po, long v, int nbits) {
+	/*
+	 * Assume signed number can be safely coerced into
+	 * unsigned of the same range.
+	 * The following testing code will likely be optimized out
+	 * by compiler if it is true.
+	 */
+	unsigned long uvalue1 = ULONG_MAX;
+	         long svalue  = uvalue1;
+	unsigned long uvalue2 = svalue;
+	assert(uvalue1 == uvalue2);
+	return uper_put_constrained_whole_number_u(po, v, nbits);
+}
+
+int uper_put_constrained_whole_number_u(asn_per_outp_t *po, unsigned long v, int nbits) {
+	if(nbits <= 31) {
+		return per_put_few_bits(po, v, nbits);
+	} else {
+		/* Put higher portion first, followed by lower 31-bit */
+		if(uper_put_constrained_whole_number_u(po, v >> 31, nbits - 31))
+			return -1;
+		return per_put_few_bits(po, v, 31);
+	}
+}
+
 /*
  * Put a small number of bits (<= 31).
  */
diff --git a/skeletons/per_support.h b/skeletons/per_support.h
index 7cb1a0c..10c84ed 100644
--- a/skeletons/per_support.h
+++ b/skeletons/per_support.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2006, 2007 Lev Walkin <vlm@lionet.info>.
+ * Copyright (c) 2005-2014 Lev Walkin <vlm@lionet.info>.
  * All rights reserved.
  * Redistribution and modifications are permitted subject to BSD license.
  */
@@ -81,6 +81,9 @@
  */
 ssize_t uper_get_nsnnwn(asn_per_data_t *pd);
 
+/* X.691-2008/11, #11.5.6 */
+int uper_get_constrained_whole_number(asn_per_data_t *pd, unsigned long *v, int nbits);
+
 /* Non-thread-safe debugging function, don't use it */
 char *per_data_string(asn_per_data_t *pd);
 
@@ -103,6 +106,10 @@
 /* Output a large number of bits */
 int per_put_many_bits(asn_per_outp_t *po, const uint8_t *src, int put_nbits);
 
+/* X.691-2008/11, #11.5 */
+int uper_put_constrained_whole_number_s(asn_per_outp_t *po, long v, int nbits);
+int uper_put_constrained_whole_number_u(asn_per_outp_t *po, unsigned long v, int nbits);
+
 /*
  * Put the length "n" to the Unaligned PER stream.
  * This function returns the number of units which may be flushed
diff --git a/tests/127-per-long-OK.asn1 b/tests/127-per-long-OK.asn1
index ad1c500..0a7b6fe 100644
--- a/tests/127-per-long-OK.asn1
+++ b/tests/127-per-long-OK.asn1
@@ -1,5 +1,6 @@
 
 -- OK: Everything is fine
+-- Also see .134 for wider integer types.
 
 -- iso.org.dod.internet.private.enterprise (1.3.6.1.4.1)
 -- .spelio.software.asn1c.test (9363.1.5.1)
@@ -12,6 +13,7 @@
 BEGIN
 
 	T ::= SEQUENCE {
+		-- Should be supported on all 32-bit platforms and above.
 		small32range	INTEGER (-2000000000..2000000000),
 		full32range	INTEGER (-2147483648..2147483647),
 		unsigned32	INTEGER (0..4294967295),
diff --git a/tests/134-per-long-OK.asn1 b/tests/134-per-long-OK.asn1
new file mode 100644
index 0000000..02e6e66
--- /dev/null
+++ b/tests/134-per-long-OK.asn1
@@ -0,0 +1,21 @@
+
+-- OK: Everything is fine
+-- Also see .127 for narrower integer types.
+
+-- iso.org.dod.internet.private.enterprise (1.3.6.1.4.1)
+-- .spelio.software.asn1c.test (9363.1.5.1)
+-- .134
+
+ModulePERLong
+	{ iso org(3) dod(6) internet (1) private(4) enterprise(1)
+		spelio(9363) software(1) asn1c(5) test(1) 134 }
+	DEFINITIONS AUTOMATIC TAGS ::=
+BEGIN
+
+    -- Supported only on 64-bit platforms.
+	T ::= SEQUENCE {
+		unsigned33  INTEGER (0..5000000000),    -- range 33 bits
+		unsigned42  INTEGER (0..3153600000000)  -- range 42 bits
+	}
+
+END
diff --git a/tests/134-per-long-OK.asn1.-Pgen-PER b/tests/134-per-long-OK.asn1.-Pgen-PER
new file mode 100644
index 0000000..f64c099
--- /dev/null
+++ b/tests/134-per-long-OK.asn1.-Pgen-PER
@@ -0,0 +1,161 @@
+
+/*** <<< INCLUDES [T] >>> ***/
+
+#include <INTEGER.h>
+#include <constr_SEQUENCE.h>
+
+/*** <<< TYPE-DECLS [T] >>> ***/
+
+typedef struct T {
+	INTEGER_t	 unsigned33;
+	INTEGER_t	 unsigned42;
+	
+	/* Context for parsing across buffer boundaries */
+	asn_struct_ctx_t _asn_ctx;
+} T_t;
+
+/*** <<< FUNC-DECLS [T] >>> ***/
+
+extern asn_TYPE_descriptor_t asn_DEF_T;
+
+/*** <<< CODE [T] >>> ***/
+
+static int
+memb_unsigned33_constraint_1(asn_TYPE_descriptor_t *td, const void *sptr,
+			asn_app_constraint_failed_f *ctfailcb, void *app_key) {
+	const INTEGER_t *st = (const INTEGER_t *)sptr;
+	long value;
+	
+	if(!sptr) {
+		_ASN_CTFAIL(app_key, td, sptr,
+			"%s: value not given (%s:%d)",
+			td->name, __FILE__, __LINE__);
+		return -1;
+	}
+	
+	if(asn_INTEGER2long(st, &value)) {
+		_ASN_CTFAIL(app_key, td, sptr,
+			"%s: value too large (%s:%d)",
+			td->name, __FILE__, __LINE__);
+		return -1;
+	}
+	
+	if((value >= 0 && value <= 5000000000)) {
+		/* Constraint check succeeded */
+		return 0;
+	} else {
+		_ASN_CTFAIL(app_key, td, sptr,
+			"%s: constraint failed (%s:%d)",
+			td->name, __FILE__, __LINE__);
+		return -1;
+	}
+}
+
+static int
+memb_unsigned42_constraint_1(asn_TYPE_descriptor_t *td, const void *sptr,
+			asn_app_constraint_failed_f *ctfailcb, void *app_key) {
+	const INTEGER_t *st = (const INTEGER_t *)sptr;
+	long value;
+	
+	if(!sptr) {
+		_ASN_CTFAIL(app_key, td, sptr,
+			"%s: value not given (%s:%d)",
+			td->name, __FILE__, __LINE__);
+		return -1;
+	}
+	
+	if(asn_INTEGER2long(st, &value)) {
+		_ASN_CTFAIL(app_key, td, sptr,
+			"%s: value too large (%s:%d)",
+			td->name, __FILE__, __LINE__);
+		return -1;
+	}
+	
+	if((value >= 0 && value <= 3153600000000)) {
+		/* Constraint check succeeded */
+		return 0;
+	} else {
+		_ASN_CTFAIL(app_key, td, sptr,
+			"%s: constraint failed (%s:%d)",
+			td->name, __FILE__, __LINE__);
+		return -1;
+	}
+}
+
+
+/*** <<< CTDEFS [T] >>> ***/
+
+static asn_per_constraints_t asn_PER_memb_unsigned33_constr_2 GCC_NOTUSED = {
+	{ APC_CONSTRAINED,	 33, -1,  0,  5000000000 }	/* (0..5000000000) */,
+	{ APC_UNCONSTRAINED,	-1, -1,  0,  0 },
+	0, 0	/* No PER value map */
+};
+static asn_per_constraints_t asn_PER_memb_unsigned42_constr_3 GCC_NOTUSED = {
+	{ APC_CONSTRAINED,	 42, -1,  0,  3153600000000 }	/* (0..3153600000000) */,
+	{ APC_UNCONSTRAINED,	-1, -1,  0,  0 },
+	0, 0	/* No PER value map */
+};
+
+/*** <<< STAT-DEFS [T] >>> ***/
+
+static asn_TYPE_member_t asn_MBR_T_1[] = {
+	{ ATF_NOFLAGS, 0, offsetof(struct T, unsigned33),
+		.tag = (ASN_TAG_CLASS_CONTEXT | (0 << 2)),
+		.tag_mode = -1,	/* IMPLICIT tag at current level */
+		.type = &asn_DEF_INTEGER,
+		.memb_constraints = memb_unsigned33_constraint_1,
+		.per_constraints = &asn_PER_memb_unsigned33_constr_2,
+		.default_value = 0,
+		.name = "unsigned33"
+		},
+	{ ATF_NOFLAGS, 0, offsetof(struct T, unsigned42),
+		.tag = (ASN_TAG_CLASS_CONTEXT | (1 << 2)),
+		.tag_mode = -1,	/* IMPLICIT tag at current level */
+		.type = &asn_DEF_INTEGER,
+		.memb_constraints = memb_unsigned42_constraint_1,
+		.per_constraints = &asn_PER_memb_unsigned42_constr_3,
+		.default_value = 0,
+		.name = "unsigned42"
+		},
+};
+static ber_tlv_tag_t asn_DEF_T_tags_1[] = {
+	(ASN_TAG_CLASS_UNIVERSAL | (16 << 2))
+};
+static asn_TYPE_tag2member_t asn_MAP_T_tag2el_1[] = {
+    { (ASN_TAG_CLASS_CONTEXT | (0 << 2)), 0, 0, 0 }, /* unsigned33 */
+    { (ASN_TAG_CLASS_CONTEXT | (1 << 2)), 1, 0, 0 } /* unsigned42 */
+};
+static asn_SEQUENCE_specifics_t asn_SPC_T_specs_1 = {
+	sizeof(struct T),
+	offsetof(struct T, _asn_ctx),
+	asn_MAP_T_tag2el_1,
+	2,	/* Count of tags in the map */
+	0, 0, 0,	/* Optional elements (not needed) */
+	-1,	/* Start extensions */
+	-1	/* Stop extensions */
+};
+asn_TYPE_descriptor_t asn_DEF_T = {
+	"T",
+	"T",
+	SEQUENCE_free,
+	SEQUENCE_print,
+	SEQUENCE_constraint,
+	SEQUENCE_decode_ber,
+	SEQUENCE_encode_der,
+	SEQUENCE_decode_xer,
+	SEQUENCE_encode_xer,
+	SEQUENCE_decode_uper,
+	SEQUENCE_encode_uper,
+	0,	/* Use generic outmost tag fetcher */
+	asn_DEF_T_tags_1,
+	sizeof(asn_DEF_T_tags_1)
+		/sizeof(asn_DEF_T_tags_1[0]), /* 1 */
+	asn_DEF_T_tags_1,	/* Same as above */
+	sizeof(asn_DEF_T_tags_1)
+		/sizeof(asn_DEF_T_tags_1[0]), /* 1 */
+	0,	/* No PER visible constraints */
+	asn_MBR_T_1,
+	2,	/* Elements count */
+	&asn_SPC_T_specs_1	/* Additional specs */
+};
+